设计模式精简图册
首发于我的公众号
设计模式分类
创建型模式:
主要用于创建对象,包括
- 工厂方法(Factory Method)
- 抽象工厂(Abstract Factory)
- 单例(Singleton)
- 生成器(Builder)
原型(Prototype)
结构型模式:
用于处理类或者对象的组合,包括
适配器(Adapter)
- 装饰者(Decorator)
- 代理(Proxy)
- 外观(Facade)
- 桥接(Bridge Pattern)
- 组合(Composite)
- 轻量(Flyweigh)
行为型模式:
用于描述类与对象怎样的交互和分配职责,包括
- 策略(Strategy)
- 观察者(Observer)
- 命令(Command)
- 模板方法(Template Method)
- 迭代器(Iterator)
- 状态(State)
- 责任链(Chain)
- 解释器(Interpreter)
- 中介者(Mediator)
- 备忘录(Memo)
- 访问者(Visitor)
设计原则
单一职责原则(Single responsibility principle)
- 核心
不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 - 问题产生
类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。 - 解决方案
遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
但是由于职责扩散会导致在实际中往往会有悖于单一职责
里氏代换原则(Liskov Substitution Principle LSP)
- 核心
所有引用基类的地方必须能透明地使用其子类的对象。 - 问题产生
有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。 - 解决方案
当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。
接口隔离原则(Interface Segregation Principle)
- 核心
不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 - 问题产生
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。 - 解决方案
将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
采用接口隔离原则对接口进行约束时,要注意以下几点:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。
迪米特原则(Law of Demeter/Least Knowledge Principle )
- 核心
迪米特法则又叫最少知道原则,一个对象应该对其他对象保持最少的了解。 - 问题产生
类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。 - 解决方案
尽量降低类与类之间的耦合。
通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息,
迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
依赖倒置原则(Dependence Inversion Principle)
- 核心
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 - 问题产生
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 - 解决方案
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒置原则的核心思想是面向接口编程,
开闭原则(Open Close Principle)
- 核心
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 - 问题产生
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。 - 解决方案
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
几个原则的关联性
用抽象构建框架,用实现扩展细节的注意事项而已:
- 单一职责原则告诉我们实现类要职责单一;
- 里氏替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要面向接口编程;
- 接口隔离原则告诉我们在设计接口的时候要精简单一;
- 迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
创建型设计模式(创建对象)
工厂方法(Factory Method Pattern)
名称 | Factory Method |
结构 | |
动机 | 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
抽象工厂(Abstract Factory Pattern)
名称 | Abstract Factory |
结构 | |
动机 | 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
生成器/建造者模式(Builder Pattern)
名称 | Builder |
结构 | |
动机 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
原型模式(Prototype Pattern)
名称 | Prototype |
结构 | |
动机 | 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
单例模式(Singleton Pattern)
名称 | Singleton |
结构 | |
动机 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
1 | public class BetterSingleton { |
创建型设计模式小结
模式 | 场景发散 | 一句话概括 |
---|---|---|
工厂方法(Factory Method) | new太多如何管理 | 生产系列产品。 |
抽象工厂(Abstract Factory) | new太多如何管理 | 一次生产多个不同产品。 |
生成器(Builder) | 车手选车 | 生产有很多组件的产品。 |
原型(Prototype) | 复制不能很难 | 克隆对象。 |
单件(Singleton) | 如何管理全局信息 | 全局只有一个。 |
结构型设计模式(处理类或者对象的组合)
桥接模式(Bridge Pattern)
名称 | Bridge |
结构 | |
动机 | 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
轻量模式/享元模式(FlyWeightPattern)
名称 | Flyweight |
结构 | |
动机 | 运用共享技术有效地支持大量细粒度的对象。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
外观模式(Facade Pattern)
名称 | Facade |
结构 | |
动机 | 为子系统中的一组接口提供一个一致的界面,F a c a d e 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
装饰者模式(Decorator Pattern)
名称 | Decorator |
结构 | |
动机 | 动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
组合模式(Composite Pattern)
名称 | Composite |
结构 | |
动机 | 将对象组合成树形结构以表示?部分-整体?的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
代理模式(Proxy Pattern)
名称 | Proxy |
结构 | |
动机 | 为其他对象提供一种代理以控制对这个对象的访问。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
适配器模式(Adapter Pattern)
名称 | Adapter |
结构 |
|
结构型设计模式小结
模式 | 场景发散 | 一句话说明 |
---|---|---|
桥(Bridge) | 麻烦的日志记录 | 将“抽象”和“实现”自由搭配。 |
轻量(Flyweight) | 森林里的树太多了 | 轻松地处理“大量”对象。 |
外观(Façade) | 超级手机 | 同时提供简单接口和复杂接口。 |
装饰者(Decorator) | 星巴克的饮料计较系统 | 不改变接口但要增强功能。 |
组合(Composite) | 超酷的绘图软件 | 不管你是老子还是儿子,都一样处理。 |
代理(Proxy) | 找中介租房 | 代理要控制你的访问,同时让你的访问更舒服 。 |
适配器(Adapter) | 老掉牙系统的重生 | 不改变功能但要改变接口 |
行为型设计模式(类与对象怎样的交互和分配职责)
观察者模式(Observer Pattern)
名称 | Observer |
结构 | |
动机 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
策略模式(Strategy Pattern)
名称 | Strategy |
结构 | |
动机 | 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
迭代器模式(Iterator Pattern)
名称 | Iterator |
结构 | |
动机 | 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
命令模式(Command Pattern)
名称 | Command |
结构 | |
动机 | 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
访问者模式(Visitor Pattern)
名称 | Visitor |
结构 | |
动机 | 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
解释器模式(Interpreter Pattern)
名称 | Interpreter |
结构 | |
动机 | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
中介者模式(Mediator Pattern)
名称 | Mediator |
结构 | |
动机 | 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
备忘录模式(Memento Pattern)
名称 | Mediator |
结构 | |
动机 | 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
模板方法模式(TemplateMethod Pattern)
名称 | Template Method |
结构 | |
动机 | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Te m p l a t e M e t h o d 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
状态模式(State Pattern)
名称 | State |
结构 | |
动机 | 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
责任链模式(Chain of Responsibility Pattern)
名称 | Chain of Responsibility |
结构 | |
动机 | 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 |
适用性 |
|
优点 |
|
缺点 |
|
小结 |
|
行为型设计模式小结
模式 | 场景发散 | 一句话说明 |
---|---|---|
观察者(Observer) | 同步更新的问题 | 一呼百应。 |
策略(Strategy) | 设计你的战士 | 分离算法。 |
迭代器(Iterator) | 集合访问的烦恼 | 以一致的方式访问集合, “松绑 ”遍历算法代码。 |
命令(Command) | 神奇的Do与Undo | 分离功能调用者与功能实现者。 |
访问者(Visitor) | 增加新方法的烦恼 | 不改变对象结构增加新方法。 |
解释器(Interpreter) | 超级表达解释引擎 | 语法解释。 |
中介者(Mediator) | 麻烦的多角关系 | 处理多对多关系。 |
备忘录(Memento) | 假如一切可以重来 | 保存对象的多个状态并可任意恢复。 |
模板方法(Template Method) | 万能的排序器 | 定义好框架算法 ,某些步骤可自己定义 。 |
状态(State) | 是攻击还是逃走 | 方便地处理不同状态不同行为,以及状态之间的转换。 |
责任链(Chain) | 邮件自动处理系统 | 用不同的规则去处理请求。 |
模式比较
参考
- http://www.uml.org.cn/sjms/201211023.asp
- http://www.cnblogs.com/chenssy/p/3357683.html
- https://academy.realm.io/cn/posts/donn-felker-solid-part-1/
- http://www.cnblogs.com/chenssy/p/3357683.html
- 《硬啃设计模式》
- 《HeadFirst 设计模式》
GoF 《可复用面向对象软件的基础》
欢迎关注我的公众号,一起学习,共同提高~