设计模式

索引: 设计模式:可复用面向对象软件的基础

《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是软件工程领域有关设计模式的一本书,提出和总结了对于一些常见软件设计问题的标准解决方案,称为软件设计模式。该书作者是埃里希·伽玛(Erich Gamma)、Richard Helm、Ralph Johnson和John Vlissides,后以“四人帮”(Gang of Four,GoF)[1]著称,书中的设计模式也被成为“四人帮设计模式”(Gang of Four design patterns)

wikipedia-design-patterns

创建型模式

简单工厂

  • 工厂类实现工厂方法, 根据传入参数的不同, 返回不同的产品实例(可以返回产品的抽象类)
  • 简单工厂类的工厂方法是静态方法, 又称为静态工厂模式
  • 问题:
    • 增加一种产品需要修改工厂方法, 增加工厂方法的复杂性
    • 由于使用了静态方法, 简单工厂无法形成基于继承的层级结构

抽象工厂

  • 抽象工厂类提供抽象工厂方法(可以有多种方法), 此类方法返回产品的抽象类;
  • 实现抽象工厂类, 实现类需要实现不同的工厂方法, 此外还需要实现 FactoryProducer 来获取不同的工厂实例;
  • 相比简单工厂:
    • 简单工厂在一个工厂方法里生产所有产品, 如果需要新增产品…需要更改具体的工厂方法;
    • 抽象工厂包含多个“工厂实现”, 每个工厂实现类只生产一类产品, 如果需要新增产品, 只需要增加新工厂类和方法;
    • 问题是, 用户代码又引入了”FactoryProducer”, 如果要新增产品和工厂, 需要修改抽象工厂类
AbstractFactory factory1 = FactoryProducer.getFactory(type1)
AbstractFactory factory2 = FactoryProducer.getFactory(type2)

AbstractProduct product1 = factory1.get()

【图】抽象工厂示例,用户代码从main()函数开始,通过 FactoryProducer 获取抽象工厂(AbstractFactory)
../_images/design-patterns-abstract-factory.png

建造者模式

  • 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  • 抽象Builder, 提供为产品设定各个属性的的抽象方法, 以及创建产品的抽象方法
  • 具体Builder, 实现…
  • 调用者创建具体的Builder实例, 调用抽象Builder类的设定属性的方法, 最后创建产品..

【图】建造者模式,从 main()函数开始,用户代码使用 MealBuilder 创建一个复杂的 Meal 对象
../_images/design-patterns-builder.png

单例模式

  • 单例模式的类, 会保证该类仅有一个实例, 并提供一个创建&获取唯一实例的方法
  • 为了保证只有一个实例, 单例类的构造函数是私有的, 不允许其他类调用其构造函数
  • 基本样式:

    class Singleton {
    private static Singleton INSTANCE;
    private Singleton();
    public Singleton getInstance(); // 获取单例的具体实现
    }
  • 实现singleton的几种方式:

    1. Lazy式, getInstance()使用synchronized 加锁
    2. Lazy式, getInstance()使用DLC(double lock check)
    3. 非Lazy式, 在static区进行实例化 // 不需要lazy loading时的首选
    4. Lazy式, 嵌套类(Singleton类里加一个static类) // 需要lazy loading时的首选
    5. 枚举 //

原型模式

原型模式(Prototype)是一种创建型设计模式,使你能够复制已有对象,而又无需使代码依赖它们所属的类。原型模式主要用于对象的复制,例如 clone()

  • 如果业务对象需要“可 clone”,一般需继承一个表示是 “cloneable” 的 interface ;
  • 业务对象实现 clone()

结构型模式

适配器模式

  • 适配器模式(Adapter Pattern)
  • 用户代码里调用 methodA(), 但是实现类没有提供 methodA() 却提供了 methodB(), 这时候需要 adaptor 类对实现类进行包装, 并提供 methodA()
  • 实现: adaptor类中, 持有一个实现类的引用 // 注意adaptor和实现类非继承关系, 而是关联关系

【图】用户代码(Client 类)通过 Adaptor 的实例,最终调用 methodB()
design-patterns-adapter

桥接模式

桥接模式软件设计模式中最复杂的模式之一,它把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。事物对象仅是一个抽象的概念。如“圆形”、“三角形”归于抽象的“形状”之下,而“画圆”、“画三角”归于实现行为的“画图”类之下,然后由“形状”调用“画图”。

  • 桥接(Bridge)模式把抽象事物/抽象行为分离开, 抽象事物调用抽象行为(的方法)
  • 桥接是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
  • 抽象事物(Abstraction)的继承类仍是抽象类(RedefinedAbstraction)
  • 抽象行为(Implementor)
  • 抽象事物(Abstraction)像桥一样把 RedefinedAbstraction 类和 Implementor 类连接在一起
  • 实现:
    • 抽象事物(Abstraction) 持有抽象行为(Implementor)的引用
    • RedefinedAbstraction extends Abstraction //实现具体事物
    • ConcreteImplementor extends Implementor //实现具体行为

【图】
design-patterns-bridge

装饰模式

  • 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
  • 增加一个修饰类, 包裹原来的类, 包裹的方式一般是通过修饰类的构造函数传入, 修饰类实现新的功能
  • 装饰模式可以避免向一个实现类添加新方法导致代码膨胀
  • 下图中的 Decorator类即装饰类, 装饰类可以改写operation()方法也可以增加自己的方法
  • 实现:
    • 需要被装饰的类(ConcreteComponent), 和它的基类(Component)
    • 装饰类(Decorator) 持有基类的一个引用. 事实上装饰类也继承上面的基类

design-patterns-decorator

代理模式

  • 用户代码不调用具体的功能类, 而是通过调用代理类间接调用这些功能类 (用户代码往往调用的是代理类的抽象接口, 代理也不直接调用功能类, 而是调用功能类的接口)
  • 代理类可以调用多种功能类, 并且可以在代理类中, 增加一些功能 @link [[../12.Java/Java-Tutorials.14.代理(Proxy)]]
  • 实现:
    • 被代理的类(RealSubject), 需要有一个接口(Subject)
    • 创建Proxy类, Proxy类也实现该接口(Subject)
    • 用户(client) 不直接调用 realSubject.DoAction(), 调用的是接口(Subject)的方法,然后是 Proxy的DoAction(), 最终调用 realSubject.DoAction()
    • 可以在代理类 Proxy::DoAction()中做一些额外的操作: e.g. 实现引用计数 & Java的AOP(面向切面编程)

design-patterns-proxy

享元模式

  • 享元模式(Flyweight Pattern),创建大量对象时, 把这些对象共有的部分抽象出来单独存储, 这些对象共享共有部分, 而不是重复创建
  • 享元模式通过共享数据减少内存使用量
  • 例子: Java String Pool、Java Intger

行为模式

责任链模式

  • 实现责任链模式的抽象类, 通常具有一个next属性
  • 实现类判断是否在自己这一层进行处理, 然后传递给next指向的对象
  • 例如一条日志具有 debug/info/warning/error几个级别, 每个实现类判断自己是否需要处理(自己的优先级同这条日志的优先级比较), 然后传给下一个实现类

观察者模式

  • 观察者 attach到被观察者, 被观察者发生改变时, 通知观察者
  • 下图中, Observer是观察者, Subject作为被观察者的抽象类, 提供了attach和detach观察者的方法(add和delete)
  • 实现:
    • 被观察者( Subject) 持有观察者(Observer)的对象
    • 注意二者关系是聚合关系(一种松散的关联关系, 二者不必有共同生命周期, Observer可以随时attach/detach到Subject)

../_images/design-patterns-observer.png

JDK里的设计模式(zz)

  • 创建模式:
    - 抽象工厂模式:抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型,如 `java.util.Calendar#getInstance()`。
    - 建造模式(Builder):定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建,如:`java.lang.StringBuilder#append()`。
    - 工厂方法:一个返回具体对象的方法,而不是多个,如 `java.lang.Object#toString()`、`java.lang.Class#newInstance()`。
    - 原型模式:使得类的实例能够生成自身的拷贝、如:`java.lang.Object#clone()`。
    - 单例模式:全局只有一个实例,如 `java.lang.Runtime#getRuntime()`。
    
  • 结构型模式:
    - 适配器:用来把一个接口转化成另一个接口,如 `java.util.Arrays#asList()`。
    - 桥接模式:这个模式将抽象和抽象操作的实现进行了解耦,这样使得抽象和实现可以独立地变化,如JDBC;
    - 组合模式:使得客户端看来单个对象和对象的组合是同等的。换句话说,某个类型的方法同时也接受自身类型作为参数,如 `Map.putAll`,`List.addAll`、`Set.addAll`。
    - 装饰者模式:动态的给一个对象附加额外的功能,这也是子类的一种替代方式,如 `java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap`。
    - 享元模式:使用缓存来加速大量小对象的访问时间,如 valueOf(int)。
    - 代理模式:代理模式是用一个简单的对象来代替一个复杂的或者创建耗时的对象,如 `java.lang.reflect.Proxy`
    
  • 行为模式:
    - 命令模式:将操作封装到对象内,以便存储,传递和返回,如:`java.lang.Runnable`。
    - 解释器模式:定义了一个语言的语法,然后解析相应语法的语句,如,`java.text.Format`,`java.text.Normalizer`。
    - 迭代器模式:提供一个一致的方法来顺序访问集合中的对象,如 `java.util.Iterator`。
    - 中介者模式:通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖,`java.lang.reflect.Method#invoke()`。
    - 观察者模式:它使得一个对象可以灵活的将消息发送给感兴趣的对象,如 `java.util.EventListener`。
    - 责任链模式:通过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象间的解耦。如 `javax.servlet.Filter#doFilter()`。
    - 空对象模式:如 `java.util.Collections#emptyList()`。
    - 模板方法模式:让子类可以重写方法的一部分,而不是整个重写,如 `java.util.Collections#sort()`。
    

@ref:

关于UML

UML参考: 设计模式-UML类图