设计模式
目录
参考资料:
设计模式的代码并没有严格的规范,设计模式的重点是"模式",或者说设计思想
用事件/案例来描述明显是个不错的方法。
代码实现存在一些问题,请注意
设计模式四重境界:
- 没学过一点不懂,想不到设计模式,写代码结构混乱
- 学了一些设计模式,想要用但不太会,经常造成额外的麻烦
- 学完了设计模式,但因为相似度高不能很好地区别使用
- 设计模式不用刻意注意,自然而然就用上了
分类
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
---创建型模式---
创建型模式 (Creational Pattern) 对类的实例化过程进 行了抽象,能够 将软件模块中对象的创建和对象的使用 分离 。为了使软件的结构更加清晰,外界对于这些对象 只需要知道它们共同的接口,而不清楚其具体的实现细 节,使整个系统的设计更加符合单一职责原则。
创建型模式在 创建什么 (What) , 由谁创建 (Who) , 何 时创建 (When) 等方面都为软件设计者提供了尽可能大 的灵活性。创建型模式 隐藏了类的实例的创建细节,通 过隐藏对象如何被创建和组合在一起达到使整个系统独 立的目的 。
工厂方法模式
工厂方法模式有 3 个类型:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
需求
生成一些类似的对象,比如披萨,有不同的口味,比如芝士、希腊、胡椒等。
不止是属性上的区别,还有制作方式上的区别。
1 简单工厂模式
定义了一个创建对象的方法,然后用类封装这个方法。
// 简单批萨工厂
public class SimplePizzaFactory {
// 创建不同口味的批萨
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new CheesePizza();
} else if (ordertype.equals("greek")) {
pizza = new GreekPizza();
} else if (ordertype.equals("pepper")) {
pizza = new PepperPizza();
}
return pizza;
}
}
缺点:
- 如果想要拓展程序,必须对工厂类进行修改(违背了开闭原则)
2 工厂方法模式
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
当增加新的类时,只需要增加一个具体的类和具体的工厂类即可,不需要修改之前创建类的代码。
abstract Pizza createPizza();
public class LDOrderPizza extends OrderPizza {
Pizza createPizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
public class NYOrderPizza extends OrderPizza {
Pizza createPizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new NYCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new NYPepperPizza();
}
return pizza;
}
}
// 使用过程
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza = new NYOrderPizza();
Pizza pizza = mOrderPizza.createPizza("cheese");
}
}
为什么不设置 2 个参数?(一个地区一个类型)
因为这样需要更改之前的代码,违背了开闭原则。
缺点:
2 个工厂类可能使用不同的创建方法,比如一个使用简单工厂模式,一个使用工厂方法模式。这就导致你在使用的时候,需要知道具体的工厂类,才能创建对象。
3 抽象工厂模式
将工厂本身也抽象成接口,这样就可以创建拥有统一规范的不同的工厂。
当增加了新的产品族时,只需要增加一个新的具体工厂类即可,不需要修改之前的代码。
public interface AbsFactory {
Pizza CreatePizza(String ordertype) ;
}
public class LDFactory implements AbsFactory {
@Override
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if ("cheese".equals(ordertype)) {
pizza = new LDCheesePizza();
} else if ("pepper".equals(ordertype)) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza = new OrderPizza("London");
Pizza pizza = mOrderPizza.createPizza("cheese");
}
}
单例模式
确保一个类最多只有一个实例,并提供一个全局访问点
比如一个 QQ 程序只能登陆一个账号,不需要登陆更多的账号。
预加载
当程序启动的时候,就初始化单例对象(多线程安全)
public class PreloadSingleton {
public static PreloadSingleton instance = new PreloadSingleton();
//其他的类无法实例化单例类的对象
private PreloadSingleton() {}
public static PreloadSingleton getInstance() {
return instance;
}
}
缺点:浪费内存
懒加载
当程序需要的时候,才初始化单例对象(多线程不安全)
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺点:多线程不安全
生成器模式
参考资料:
封装一个复杂对象构造过程,并允许按步骤构造。
定义解释: 我们可以将生成器模式理解为,假设我们有一个对象需要建立,这个对象是由多个组件(Component)组合而成,每个组件的建立都比较复杂,但运用组件来建立所需的对象非常简单,所以我们就可以将构建复杂组件的步骤与运用组件构建对象分离,使用 builder 模式可以建立。
生成器模式结构中包括四种角色:
(1)产品(Product):具体生产器要构造的复杂对象;
(2)抽象生成器(Bulider):抽象生成器是一个接口,该接口除了为创建一个 Product 对象的各个组件定义了若干个方法之外,还要定义返回 Product 对象的方法(定义构造步骤);
(3)具体生产器(ConcreteBuilder):实现 Builder 接口的类,具体生成器将实现 Builder 接口所定义的方法(生产各个组件);
(4)指挥者(Director):指挥者是一个类,该类需要含有 Builder 接口声明的变量。指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器类来构造用户所需要的 Product 对象,如果所请求的具体生成器成功地构造出 Product 对象,指挥者就可以让该具体生产器返回所构造的 Product 对象。(按照步骤组装部件,并返回 Product)
用于生成过程比较复杂的对象,由各个部件组成,而各个部件的生成过程比较复杂,但是组合部件的过程比较简单。
由抽象生成器来规范组件生成的过程,具体实现在具体生成器中。
实现案例
ComputerBuilder 类定义构造步骤:
public abstract class ComputerBuilder {
protected Computer computer;
public Computer getComputer() {
return computer;
}
public void buildComputer() {
computer = new Computer();
System.out.println("生成了一台电脑!!!");
}
public abstract void buildMaster();
public abstract void buildScreen();
public abstract void buildKeyboard();
public abstract void buildMouse();
public abstract void buildAudio();
}
HPComputerBuilder 定义各个组件:
public class HPComputerBuilder extends ComputerBuilder {
@Override
public void buildMaster() {
// TODO Auto-generated method stub
computer.setMaster("i7,16g,512SSD,1060");
System.out.println("(i7,16g,512SSD,1060)的惠普主机");
}
@Override
public void buildScreen() {
// TODO Auto-generated method stub
computer.setScreen("1080p");
System.out.println("(1080p)的惠普显示屏");
}
@Override
public void buildKeyboard() {
// TODO Auto-generated method stub
computer.setKeyboard("cherry 青轴机械键盘");
System.out.println("(cherry 青轴机械键盘)的键盘");
}
@Override
public void buildMouse() {
// TODO Auto-generated method stub
computer.setMouse("MI 鼠标");
System.out.println("(MI 鼠标)的鼠标");
}
@Override
public void buildAudio() {
// TODO Auto-generated method stub
computer.setAudio("飞利浦 音响");
System.out.println("(飞利浦 音响)的音响");
}
}
Director 类对组件进行组装并生成产品
public class Director {
private ComputerBuilder computerBuilder;
public void setComputerBuilder(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Computer getComputer() {
return computerBuilder.getComputer();
}
public void constructComputer() {
computerBuilder.buildComputer();
computerBuilder.buildMaster();
computerBuilder.buildScreen();
computerBuilder.buildKeyboard();
computerBuilder.buildMouse();
computerBuilder.buildAudio();
}
}
优点:
- 将一个对象分解为各个组件
- 将对象组件的构造封装起来
- 可以控制整个对象的生成过程
缺点
- 对不同类型的对象需要实现不同的具体构造器的类,这可能回答大大增加类的数量
生成器模式与工厂模式的不同
生成器模式构建对象的时候,对象通常构建的过程中需要多个步骤,就像我们例子中的先有主机,再有显示屏,再有鼠标等等,生成器模式的作用就是将这些复杂的构建过程封装起来。工厂模式构建对象的时候通常就只有一个步骤,调用一个工厂方法就可以生成一个对象。
原型模式
需求:
当你需要一个和某个已有对象相似的对象,但是这个类的创建比较复杂时,可以使用原型模式。
原型模式通过抽象接口,规定了类的实现需要定义 clone 方法,这样就可以通过 clone 方法来快速复制一个对象。
当然,你可以实现深拷贝或者浅拷贝,这根据需要选择。
实现案例
// Shape.java
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType() {
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
// JAVA对象自带一个 Object.clone() 方法,进行浅拷贝
// 因为 shape 有同名的clone函数,所以这里需要使用 super.clone()
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
---结构型模式---
其描述 如何将类或者对 象结合在一起形成更大的结构 ,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。 结构型模式可以分为 类结构型模式 和 对象结构型模式 :
- 类结构型模式关心类的组合 ,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
- 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是 对象结构型模式 。
适配器模式
定义: 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
参考资料:
类适配器
通过多重继承目标接口和被适配者类方式来实现适配
举例(将 USB 接口转为 VGA 接口),类图如下:
AdapterUSB2VGA 类作为 VGA 抽象类的实现,可以和 VGAImpl 类一样使用,由此实现了 USB 接口转 VGA 接口的功能。
public class USBImpl implements USB {
@Override
public void showPPT() {
// todo Auto-generated method stub
System.out.println("PPT内容演示");
}
}
//
public class AdapterUSB2VGA extends USBImpl implements VGA {
@Override
public void projection() {
super.showPPT();
}
}
public class Projector<T> {
public void projection(T t) {
if (t instanceof VGA) {
System.out.println("开始投影");
VGA v = new VGAImpl();
v = (VGA) t;
v.projection();
} else {
System.out.println("接口不匹配,无法投影");
}
}
}
public class Projector<T> {
public void projection(T t) {
if (t instanceof VGA) {
System.out.println("开始投影");
VGA v = new VGAImpl();
v = (VGA) t;
v.projection();
} else {
System.out.println("接口不匹配,无法投影");
}
}
}
缺点:
- 由于 Java 不支持多重继承,所以这种方式只能适配一个类,如果想要适配多个类,就需要使用对象适配器模式。
- 如果 需要适配的类的实现不存在,就无法使用类适配器模式。
对象适配器模式
对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。
举例(将 USB 接口转为 VGA 接口),类图如下:
与类适配器不同,AdapterUSB2VGA 类不再继承 USBImpl 类,而是持有 USBImpl 类的实例,通过调用实例的方法来实现 VGA 接口的功能。
public class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
}
接口适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
举例(将 USB 接口转为 VGA 接口,VGA 中的 b()和 c()不会被实现),类图如下:
public abstract class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
@Override
public void b() {}
@Override
public void c() {}
}
// AdapterUSB2VGA实现,不用去实现b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
public void projection() {
super.projection();
}
}
总结
总结一下三种适配器模式的应用场景:
类适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个 Wrapper 类,持有原类的一个实例,在 Wrapper 类的方法中,调用实例的方法就行。
接口适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类 Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
命名规则:
我个人理解,三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。
类适配器,以类给到,在 Adapter 里,就是将 src 当做类,继承,
对象适配器,以对象给到,在 Adapter 里,将 src 作为一个对象,持有。
接口适配器,以接口给到,在 Adapter 里,将 src 作为一个接口,实现。
使用选择:
根据合成复用原则,组合大于继承。因此,类的适配器模式应该少用。
装饰者模式
定义:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。
1.Component(被装饰对象的基类)
定义一个对象接口,可以给这些对象动态地添加职责。
2.ConcreteComponent(具体被装饰对象)
定义一个对象,可以给这个对象添加一些职责。
3.Decorator(装饰者抽象类)
维持一个指向 Component 实例的引用,并定义一个与 Component 接口一致的接口。
4.ConcreteDecorator(具体装饰者)
具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责。
被装饰对象和修饰者继承自同一个超类
举例(咖啡馆订单项目:1)、咖啡种类:Espresso、ShortBlack、LongBlack、Decaf2)、调料(装饰者):Milk、Soy、Chocolate),类图如下:
被装饰的对象和装饰者都继承自同一个超类
// 饮品超类
public abstract class Drink {
public String description = "";
private float price = 0f;
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return description + "-" + this.getPrice();
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public abstract float cost();
}
被装饰的对象,不用去改造。原来怎么样写,现在还是怎么写。
// 被装饰的对象
public class Coffee extends Drink {
@Override
public float cost() {
// TODO Auto-generated method stub
return super.getPrice();
}
}
装饰者
装饰者不仅要考虑自身,还要考虑被它修饰的对象,它是在被修饰的对象上继续添加修饰。例如,咖啡里面加牛奶,再加巧克力。加糖后价格为 coffee+milk。再加牛奶价格为 coffee+milk+chocolate。
public class Decorator extends Drink {
private Drink Obj;
// 创建的时候,聚合被修饰者
public Decorator(Drink Obj) {
this.Obj = Obj;
}
@Override
public float cost() {
// todo Auto-generated method stub
return super.getPrice() + Obj.cost();
}
@Override
public String getDescription() {
return (
super.description + "-" + super.getPrice() + "&&" + Obj.getDescription()
);
}
}
装饰者实例化(加牛奶)。这里面要对被修饰的对象进行实例化。
public class Milk extends Decorator {
public Milk(Drink Obj) {
super(Obj);
// TODO Auto-generated constructor stub
super.setDescription("Milk");
super.setPrice(2.0f);
}
}
coffee 店:初始化一个被修饰对象,修饰者实例需要对被修改者实例化,才能对具体的被修饰者进行修饰。
public class CoffeeBar {
public static void main(String[] args) {
Drink order;
order = new Decaf();
System.out.println("order1 price:" + order.cost());
System.out.println("order1 desc:" + order.getDescription());
System.out.println("****************");
order = new LongBlack();
order = new Milk(order);
order = new Chocolate(order);
order = new Chocolate(order);
System.out.println("order2 price:" + order.cost());
System.out.println("order2 desc:" + order.getDescription());
}
}
总结
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。
代理模式
通过定义一个类来代理另一个类的功能。
作用:
- 可以在做到在实现功能前后添加操作的功能,比如打日志,与处理数据等,并且符合开闭原则
- 可以将复杂的过程组装成一个接口供外部使用
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
静态代理
按照图示的方法,定义一个代理类,实现和被代理类相同(或者更多)的接口,然后在代理类中持有被代理类的实例,通过代理类的方法来调用被代理类的方法。
// 创建服务类接口
public interface BuyHouse {
void buyHosue();
}
// 实现服务接口
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要买房");
}
}
// 创建代理类
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(final BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHosue() {
System.out.println("买房前准备");
buyHouse.buyHosue();
System.out.println("买房后装修");
}
}
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点: 代理对象与目标对象要实现相同的接口,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
特点
- 代理对象,不需要实现接口
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 代理类不用再实现接口了。但是,要求被代理对象必须有接口。
关键 API
/**
* Java.lang.reflect.Proxy.newProxyInstance()
* 获得一个代理对象
* @param loader 类加载器 一般使用被代理对象的类加载器
* @param interfaces 代理类需要实现的接口列表 一般使用的被代理对象实现的接口
* @param h InvocationHandler 接口的实现类实例
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
// 编写动态处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("买房前准备");
Object result = method.invoke(object, args);
System.out.println("买房后装修");
return result;
}
}
// InvocationHandler中的invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行
// 参数1:代理对象(慎用)
// 参数2:当前执行的方法
// 参数3:当前执行的方法运行时传递过来的参数
// 编写测试类
public class DynamicProxyTest {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(
BuyHouse.class.getClassLoader(),
new Class[] { BuyHouse.class },
new DynamicProxyHandler(buyHouse)
);
proxyBuyHouse.buyHosue();
}
}
动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏(我们要使用被代理的对象的接口),因为它的设计注定了这个遗憾。
CGLIB 代理
咕咕咕?
外观模式
定义: 隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。
简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,设计到 3 个角色。
1).门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。(客户调用,同时自身调用子系统功能)
2).子系统角色:实现了子系统的功能。它对客户角色和 Facade 时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。(实现具体功能)
3).客户角色:通过调用 Facede 来完成要实现的功能(调用门面角色)。
举例(每个 Computer 都有 CPU、Memory、Disk。在 Computer 开启和关闭的时候,相应的部件也会开启和关闭),类图如下:
public class CPU {
public void start() {
System.out.println("cpu is start...");
}
public void shutDown() {
System.out.println("CPU is shutDown...");
}
}
public class Disk {
public void start() {
System.out.println("Disk is start...");
}
public void shutDown() {
System.out.println("Disk is shutDown...");
}
}
public class Memory {
public void start() {
System.out.println("Memory is start...");
}
public void shutDown() {
System.out.println("Memory is shutDown...");
}
}
// 然后是,门面类Facade
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer() {
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void start() {
System.out.println("Computer start begin");
cpu.start();
disk.start();
memory.start();
System.out.println("Computer start end");
}
public void shutDown() {
System.out.println("Computer shutDown begin");
cpu.shutDown();
disk.shutDown();
memory.shutDown();
System.out.println("Computer shutDown end...");
}
}
// 客户角色
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();
System.out.println("=================");
computer.shutDown();
}
}
优点
- 松散耦合
使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
- 简单易用
客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟 Facade 类交互即可。
- 更好的划分访问层次
有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。
桥接模式
一句话秒懂:将 m*n 个实现类转换为 m+n 个实现类
定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
参考资料
适用场景
桥接模式通常适用于以下场景:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
案例
情况描述:
开发多个应用的前端页面,要求在多平台上实现
Web 端和 PC 端为用户端的具体实现,有显示和渲染等功能
问题描述:
如果想要增加一个 APP 的话,需要为所有平台都写一个网页,增加要实现的平台也同理。
代码复用率低,类个数随着平台和 APP 增加而大幅增加,维护成本高
APP 继承了一些不需要的功能,比如说:渲染功能不需要由 APP 来实现。
思考:
继承方法导致父类和子类之间的高耦合性,当平台修改时,非常麻烦。
引起整个结构变化的元素有两个,一个是用户端平台,一个是 APP,所以我们将这两个点抽出来,分别进行封装,使用的时候再进行组合。
方案:
将平台抽象化,将 APP 现实化,两者之间通过桥接进行组合。
那个维度抽象那个维度实现,应根据具体情况分析
桥接模式图:
Abstraction:抽象化角色,抽象化给出的定义,并保存一个对实现化对象的引用。
通过引用实现化对象,抽象化角色可以调用实现化角色中的方法。这个就是桥接模式的桥接部分。
RefinedAbstraction:修正抽象化角色,扩展抽象化角色,改变和修正父类对抽象化的定义。
Implementor:实现化角色,给出实现化角色的接口,但不给出具体的实现。
ConcreteImplementor:具体实现化角色,给出实现化角色接口的具体实现。
这里我不知道为什么平台是继承(实线),而 APP 是实现(虚线)
桥接模式代码示例
public interface App {
public void run();
}
public class App1 implements App {
@Override
public void run() {
System.out.println("run app1");
}
}
public class App2 implements App {
@Override
public void run() {
System.out.println("run app2");
}
}
public abstract class Client {
protected Software software;
public void setSoftware(Software software) {
this.software = software;
}
public abstract void run();
}
public class Web extends Client {
@Override
public void run() {
// 使用浏览器运行APP
software.run();
}
}
public class PC extends Client {
@Override
public void run() {
// 使用electron运行APP
software.run();
}
}
对比最初的设计,将抽象部分(手机)与它的实现部分(手机软件类)分离,将实现部分抽象成单独的类,使它们都可以独立地变化。整个类图看起来像一座桥,所以称为桥接模式
继承是一种强耦合关系,子类的实现与它的父类有非常紧密的依赖关系,父类的任何变化 都会导致子类发生变化,因此继承或者说强耦合关系严重影响了类的灵活性,并最终限制了可复用性
从桥接模式的设计上我们可以看出聚合是一种比继承要弱的关联关系,手机类和软件类都可独立的进行变化,不会互相影响
优缺点
优点:
(1)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
(2)桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。
缺点:
桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
---行为型模式---
创建型模式 (Creational Pattern) 对类的实例化过程进 行了抽象,能够 将软件模块中对象的创建和对象的使用 分离 。为了使软件的结构更加清晰,外界对于这些对象 只需要知道它们共同的接口,而不清楚其具体的实现细 节,使整个系统的设计更加符合单一职责原则。
创建型模式在 创建什么 (What) , 由谁创建 (Who) , 何 时创建 (When) 等方面都为软件设计者提供了尽可能大 的灵活性。创建型模式 隐藏了类的实例的创建细节,通 过隐藏对象如何被创建和组合在一起达到使整个系统独 立的目的 。
观察者模式
定义: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
优点:
1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
- 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
- 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己
- 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
- 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调。
举例(有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息。)类图如下:
1、定义一个抽象被观察者接口
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
2、定义一个抽象观察者接口
public interface Observer {
public void update(String message);
}
3、定义被观察者,实现了 Observerable 接口,对 Observerable 接口的三个方法进行了具体实现,同时有一个 List 集合,用以保存注册的观察者,等需要通知观察者时,遍历该集合即可。
public class WechatServer implements Subject {
private List<Observer> list;
private String message;
public WechatServer() {
list = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
// TODO Auto-generated method stub
list.add(o);
}
@Override
public void removeObserver(Observer o) {
// TODO Auto-generated method stub
if (!list.isEmpty()) {
list.remove(o);
}
}
@Override
public void notifyObserver() {
// TODO Auto-generated method stub
for (Observer o : list) {
o.update(message);
}
}
public void setInfomation(String s) {
this.message = s;
System.out.println("微信服务更新消息: " + s);
// 消息更新,通知所有观察者
notifyObserver();
}
}
4、定义具体观察者,微信公众号的具体观察者为用户 User
public class User implements Observer {
private String name;
private String message;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
this.message = message;
read();
}
public void read() {
System.out.println(name + " 收到推送消息: " + message);
}
}
5、编写一个测试类
public class MainTest {
public static void main(String[] args) {
WechatServer server = new WechatServer();
Observer userZhang = new User("ZhangSan");
Observer userLi = new User("LiSi");
Observer userWang = new User("WangWu");
server.registerObserver(userZhang);
server.registerObserver(userLi);
server.registerObserver(userWang);
server.setInfomation("PHP是世界上最好用的语言!");
System.out.println("----------------------------------------------");
server.removeObserver(userZhang);
server.setInfomation("JAVA是世界上最好用的语言!");
}
}