总结记录23种设计模式类图和思想原理。
根据GoF的经典著作,将设计模式分为3大类。分别为创建型,结构型,行为型。
创建型:与对象的创建有关
结构型:处理类或对象的组合
行为型:对类和对象怎样交互和怎样分配职责进行描述
创建型设计模型
单例模式
- 当存在多个实例时可能会相互影响,因此确保只有一个实例
- 需要关注何时生成这个实例(是否需要惰性生成?)
原型模式
- 对象种类繁多,无法将它们整合到一个类中时
- 难以根据类生成实例时
- 想解耦框架与生成实例时
- 一旦在代码中出现要使用的类的名字,就无法与该类分离开类,也就无法实现复用
典型的原型模式是JAVA中java.lang.Cloneable接口,另外javascript也是基于原型模式的语言
Builder模式(建造者)
时序图
- 建造者模式用于构建复杂的对象
- 建造者模式的好处时,使用者可以用不同的建造者来构造不同种类的对象
- 可能容易和抽象工厂混淆,建造者模式更多的关注的是整体的构建,抽象工厂更多的是关注的每个零件的构建
- 建造者模式设计时的方法能不能在后面易于添加新的建造者十分考验功力
抽象工厂
- 易于增加具体的工厂
- 难以增加新的零件
工厂方法
- 框架与具体加工,框架无需修改
- 使用模式与开发人员沟通。使用模式设计类时,必须要向维护这些类的开发人员正确地传达这些设计模式的意图。否则,维护人员在修改设计时可能会违背设计者的最初意图
结构型设计模式
适配器
使用继承
使用委托
- 可以对现有的类进行适配
- 实际上,我们在让现有类适配新接口时,常常会有"只要将这里稍微修改下就可以的想法"一不留神就会修改现有代码
- 版本升级与兼容性
- 功能完全不同的类是无法使用的
桥接模式
将类的功能层次结构与实现层次结构分离
- 分开后更容易扩展,增加后的功能可以被所有实现使用
- 继承是强关联,委托是弱关联
组合模式
考虑文件系统中的目录和普通文件
- 多个和单个的一致性
- 到处都存在递归结构
装饰器模式
- 接口(API)的透明性
- 在不改变被装饰物的前提下增加功能
- 可以动态地增加功能
- 只需要一些装饰物就可添加许多功能
外观模式
- 接口API变少了
- 程序与外部的关联弱化了
- 当某个程序员得意地说出"啊,在调用那个类之前需要先调用这个类。在调用那个方法之前需要先在这个类中注册一下"的时候,就意味着需要引入facade模式了
- 对于那些能够明确用语言描述出来的知识,我们不应该将它们隐藏在自己的脑袋中,而是应该用代码将它们表现出来
享元模式
类似于缓存系统实现,尽量复用之前的对象
- 共享对象在变化时会对多个地方产生影响
- 不要让被共享的实例被垃圾回收器回收了
- 内存之外的资源
代理模式
- 使用代理人来提升处理速度
- 代理与委托
- 透明性
- HTTP代理
- 各种proxy模式:Virtual Proxy,Remote Proxy,Access Proxy
行为型设计模式
责任链
- 弱化请求者与处理者间的关系
- 可以动态地改变职责链
- 处理者专注自己的工作
命令模式
- 命令中应该包含哪些信息
- 保存历史记录
- 适配器
迭代器
- 不管实现如何变化,都可以使用Iterator 来遍历对象
仲裁者
典型例子:用户登陆界面有各种输入控件,由统一的仲裁者判断输入是否合法,能否提交。仲裁者持有所有输入控件,能够对当前控件状态进行统一仲裁
- 当发生分散灾难时
- 通信线路的增加
- 哪些角色可以复用,依赖于特定应用程序就意味着难以复用
备忘录
注意Memnto中的不同接口类型,宽接口由于会暴露内部状态,因此只能由Originator使用
- 两种接口(API)和可见性
- 需要多少个Memento
- Memento的有效期限是多久
- 划分Caretaker角色和Originator角色的意义。实现职责分担:变更为可以多次撤销;变更为不仅可以撤销,还可以将现在的状态保存在文件中
典型例子:游戏中保存当前状态以便日后恢复
观察者
发布者订阅模式,典型的通过消息队列解耦。
- 体现出了可替换性
- Observer的顺序
- 当Observer的行为会对Subject产生影响时
状态模式
- 分而治之
- 依赖于状态的处理
- 应当是谁来管理状态的迁移
- 不会自相矛盾
- 易于增加新的状态
- 实例的多面性
典型编程举例:去掉C文件中的注释
策略模式
- 使用委托这种弱关联关系可以很方便地整体替换算法
- 程序运行中也可以切换策略
典型举例:动态选择不同的游戏难度级别
访问者模式
- 双重分发
- 将处理从数据结构中分离出来
- 开闭原则
- 易于增加ConcreteVisitor,难以增加ConcreteElement
解释器模式
- 正则表达式
- 检索表达式
- 批处理语言
- 跳过标记还是读取标记
模板方法
- 可以使逻辑处理通用化,当在模板方法中发现bug时,只需要修改模板方法即可
- 父类与子类紧密协作,子类需要理解抽象方法调用的时机,在看不到父类源码时,实现子类是比较困难的
- 父类与子类的一致性,LSP原则