设计模式快速复习
对 Design Pattern Explanation with C++ Implementation(By K_Eckel) 的阅读总结
创建型模式
Factory :提供一个专门用来创建对象的工厂类,而不是直接使用 new ;
- 可以根据运行时的情况来判断究竟是实例化谁,代码可读性强
- 只能用于一类类(例如有一个共同的基类),腰围不同的类提供一个对象创建的结构,需要用到 AbstractFactory
AbstractFactory:用于创建一组相关或者相互依赖的对象,基于 Factory 实现
Singleton :创建一个全局唯一的变量,即使在纯面向对象范式的编程当中,也可以不借助全局变量来实现单例。
- Singleton 不可以被实例化,而是直接调用其 getInstance( ) 等函数(往往是静态函数)来返回唯一实例
- Singleton 经常和 Factory 一起使用,因为一般来说工厂对象只需要一个
Builder:当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要将复杂对象的创建过程和这个对象的表示(展示)分离开来,由于在每一步的构造过程中可以引入参数,使得经过相同的步骤创建最后得到的对象的展示不一样。
- Builder 的关键是 Director 对象并不直接返回对象,当然也可以提供一个默认的返回对象的接口(即返回通用的复杂对象的创建,即不指定或者特定唯一指定 BuildPart 中的参数)
- Builder 和 AbstractFactory 很相似,都是用来创建大的复杂的对象。 Builder 强调一步一步创建对象,并且可以通过相同的创建过程可以获得不同的结果对象,并且不直接返回对象;AbstractFactory 强调的是为创建多个相互依赖的对象提供一个统一的接口。
Prototype 模式:新对象的创建可以通过已有对象进行创建,其他于语言常有 clone() 或 copy( ) 函数,在 C++ 中由拷贝构造函数来实现。
- 这里 Prototype 本身就是对象工厂,与前面几个工厂不同,prototype 强调从自身复制自己创建新类。
结构型模式
Bridge:使用组合(委托)的方式将抽象和实现彻底地解耦,抽象和实现可以独立地变化,减少因为需求变化而导致类的迅速膨胀。
- Bridge 的关键是在于 “favor composition over inheritance” ,即 pImpl 方法代替继承。
Adapter:转换器,我们已经实现的接口通过接口转换得到一个第三方的接口,例如 C++ 中的 stack 和 queue,实际上底层可以是 vector、list、deque 等;常有两种实现方法:类模式和对象模式,区别在于是 inheritance 还是 composition
Decorator:需要为一个已经定义好的类添加新的职责,但是又不想修改类,也不想继承某一个具体的类,就可以考虑使用 decorator;
- 注意图示里的 Operation() 被装饰以后,我们调用 Operation 的同时也会执行 addedBehavior()
- decorator 和 proxy 模式都会有一个指向其他对象的引用/指针,但 Proxy 模式会提供使用其作为代理的对象统一的接口,使用代理类将其操作都委托给 Proxy 直接进行,其实是组合和委托之间的微妙区别。
Composite:递归构建树状的组合结构(有点像广义表),每个 composite 里边可以有多个 component,每个 component 可以是具体的实现(如 Leaf),也可以是又一个 composite
- Composite 和 Decorator 的模式结构图类似,但是 Composite 模式旨在构造类,而 Decorator 模式重在不生成子类即可给对象添加职责
(图里错了,leaf 右边的是 composite 类)
Flyweight :将对象的状态分为内部状态和外部状态,内部状态可以被共享、不会变化,存储在对象中;而外部状态可以适当的时候作为参数传递给对象。
- 例如一个字母“a”在文档中出现了若干次,可以让这些字符共享“a”一个对象,其字体大小等可以作为参数传入。
- Flyweight 模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池),我觉得有点像 Java 处理 String 的方式,上面的解释反而使得 Flyweight 的意思更抽象了hhh
Facade :就有点像是我们要完成一件事情,这个事情由不同的步骤组成,而这些步骤的实现方法都分布在不同的类中,那么我们可以写一个类,将各个类都包裹起来,然后只对外维护一个接口,这样我们只需要调用这个接口,内部就可以自动地调用其他类的方法依次完成这件事情。
- Facade 在高层提供了一个统一的接口,解耦了系统
Proxy: 以下问题适合使用 Proxy 模式
- 创建开销大的对象时,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成
- 为网络上的对象创建一个局部的本地代理,我们将这个操纵的过程交给一个代理去完成
- 对对象进行控制访问的时候,比如论坛上不同权限的用户,将这个工作交给一个代理去完成
- 智能指针
Proxy 模式最大的好处就是实现了逻辑和实现的彻底解耦
行为模式
Template :采用继承的方法,将某一处理问题的逻辑框架放在抽象基类中,并且定义好细节的接口,而在子类中实现每一个步骤的实现细节。
-
本质上就是使用多态的方法实现算法实现细节和高层接口的松耦合。(想一下侯捷大佬举的操作系统的例子)
-
Template 模式体现了 DIP(依赖倒置原则),其含义就是父类调用子类的操作。
-
相比于 Strategy,Template 的问题就是继承固有的问题,但相应地 Strategy 付出了空间和时间上的代价
Strategy:Strategy 要解决的问题和 Template 是一样的,但是逻辑被封装到 Context 类里(而 Template 的逻辑封装在 AbstractClass 里),通过组合的方式将具体算法的实现在组合对象中实现
- Strategy 模式和 Template 模式实际是实现一个抽象接口的两种方式:继承和组合之间的区别,但还是优先使用组合
- Strategy 模式很 State 模式也有相似之处,但是 State 模式注重的对象在不同的状态下不同的操作。两者之间的区别就是 State 模式中具体实现类中有对 Context 的函数的引用,而 Strategy 模式则没有。
组合
- 优点
- “黑盒”复用,因为被包含对象的内部细节对外是不可见的;
- 封装性好,原因为 1.;
- 实现和抽象的依赖性很小(组合对象和被组合对象之间的依赖性小);
- 可以在运行期间动态定义实现(通过一个指向相同类型的指针,典型的是抽象基类的指针)。
- 缺点
- 系统中对象过多。
继承
- 优点
- 易于修改和扩展那些被复用的实现。
- 缺点
- 破坏了封装性,继承中父类的实现细节暴露给子类了;
- “白盒”复用,原因在 1. 中;
- 当父类的实现更改时,其所有子类将不得不随之改变
- 从父类继承而来的实现在运行期间不能改变(编译期间就已经确定了)。
State:需要对不同的状态作出不同的响应,同时分离状态逻辑和动作(而不是像 switch-case 一样全部写在一起)
- 将 State 声明为 Context 的友元类(friend class),这样才可以切换状态
- State 及其子类中的操作都将 Context*传入作为参数(传入 this 指针)
Observer:MVC 就是一种 Observer 模式,发布-订阅,目标是通知的发布者,观察者是通知的订阅者
- Subject 中有一个保存指向 Observer 引用的数组,可以发布消息,调用每个 Oberver 的 update
Memento:不破坏封装性的前提下,捕获并保存一个类的内部状态,这样就可以利用保存的状态实施恢复操作(撤销/Undo 的动作)
- Memento 模式的关键就是 friend class Originator,并且 Memento 的接口都设为 private ,这样 Originator 就可以访问到所有东西,保证了封装性
- Memento 相当于是 Originator 的一个备份
- 在 Command 模式中,Memento 模式经常被用来维护可以撤销操作的状态
Mediator :将对象间的交互和通讯封装在一个类中,个对象间的通信不用显示声明引用,大大降低了系统的复杂性能,带来了系统对象间的松耦合。
- 每个 Colleague 维护一个 Mediator,将多对多的通信转化为一对多的通信;A、B 和 Mediator 之间需要双向绑定
Command:将请求封装到一个对象(Command)中,并将请求的接收者存放到具体的 ConcreteCommand 类中(Receiver),从而实现调用操作的对象和操作的具体实现者之间的解耦。
- Command 类中一般就是只是一些接口的集合,并不包含任何的数据属性
- 在 Command 要增加新的处理操作对象很容易,我们可以通过创建新的继承自 Command 的子类来实现这一点。
- Command 模式可以和 Memento 模式结合起来,支持取消的操作
Visitor:在面对需求更新时,将更新封装到一个类中(访问操作),并由待更改类提供一个接受接口
- 这里 Element 是待更改的类,Visitor 是更新的内容
- Visitor 的关键是双分派,双分派意味着执行的操作将取决于请求的种类和接收者的类型;而 C++ 是单分派,可以通过 RTTI 来实现
- 问题:破坏了封装性,从外部可以修改 Element 对象的状态,要不然就要 Element 提供足够的 public 接口,要不然要声明 friend Visitor;ConcreteElement 的扩展很困难,每增加一个 Element 的子类,就要增加 Visitor 的接口
Chain of Responsibility:将可能处理一个请求的对象链成一个链,并且将请求在链上传递,直到有对象处理该请求(可能需要提供一个默认处理所有请求的类)
- 这里 Handler 可以相互链接成链表,每次接到请求首先看看自己可不可以处理,如果不行,就交给后继节点处理
- Chain of Responsibility 模式的最大的一个有点就是给系统降低了耦合,请求的发送者完全不必知道该请求会被哪个应答对象处理
Iterator:将一个聚合对象的遍历问题封装到一个类中进行,避免暴露这个聚合对象的内部表示,比如 C++ 容器的迭代器
- 为了更好地保护 Aggregate 的状态,可以尽可能减少它的 public 接口,而通过将 Iterator 对象声明为 Aggregate 的友元来给予Iterator一些特权,获得访问 Aggregate 私有数据和方法的机会
Interpreter:使用一个解释器,为用户提供一个一门定义语言语法表示的解释器,然后通过这个解释器来解释语言中的句子。(可以想想编译器的前端)
- Interpreter 模式中使用类来表示文法规则,因此可以很容易实现文法的扩展,可以使用 Flyweight 模式来实现终结符的共享