23种设计模式(超级简洁)
-By Jason McDonald
关于设计模式
这个设计模式参考提供四人帮23种模式的快速参考,像原书《设计模式-可复用面向对象基础》中所陈述的,每个模式包括,类图,解释,使用说明和实际例子。
创建模式:用来构建对象以便能从实现系统解耦。
结构模式:用不同的对象组成大规模的对象结构。
行为模式:用来在对象中管理算法,关系,和责任。
对象层面:处理对象之间的关系,决定于运行期。
类层面:处理类的关系,决定于在编译期。
Chain of Responsibility责任链 对象行为
目的:
通过把多个接受请求的对象链接起来,给多个对象去执行请求的机会。
使用:
- 允许多个对象处理一个请求,处理者不必是特定的对象。
- 存在一组对象可以去处理请求,动态决定到底是哪一个处理者。
- 可以接受潜在的情形是请求最终没有被任何Handler处理。
例子:
某些语言的异常处理使用此模式,当一个异常被方法抛出时,运行时检查那个方法有没有机制去处理异常。或者它应该沿调用堆栈向上传递。向上传递的过程持续,直到处理这个异常的代码找到,或者没有更外层的父类对象来处理异常。
Command命令 对象行为
目的:
封装一个请求,允许它作为一个对象来对待, 这样允许请求被作为队列(Queuing)或回调(Callbacks)中的传统对象来处理
使用:
- 你需要回调(Callbacks)功能
- 多个请求需要在不同时间或不同顺序被处理
- 请求日志需要记录
- 发起者需要和处理命令的对象(Receiver)解耦。
例子:
工作队列广泛应用于异步处理的算法。通过命令模式,要执行的功能可以发给一个工作队列,而工作队列无需知道发起的请求的实际实现,排队的命令对象用自己的算法实现了队列期望的静态接口。
Interpreter解释器 类型行为
目的:
定义一个语法表示,和一种理解处理语法的机制
使用:
语法可以被解释成一颗大的语法树。
语法简单。
效率不重要。
语法和底层的表达式解耦。
例子:
文字类的冒险游戏,1980年代相当流行,提供了一个很好的例子。 很多都有简单的命令,比如“向下走”允许对游戏的遍历。这些命令可以嵌套,如此得以改变他们的意思。例如,“go in”将导致和”go up”不同的结果。基于命令和修饰符(终结和非终结表达式)创建出的命令等级系统,应用可以很容易映射很多不同命令到一个行为树。
Mediator调停者 对象行为
目的:
通过封装不同的对象集之间的交互和通讯实现松耦合, 允许每个对象集的行为相对于其他对象独立的演化。
使用:
不同对象集之间通信事先被定义,并且复杂。
通信和控制的节点之间存在太多的关系。
例子:
邮件列表软件记录登记到邮件列表的用户,并提供一个入口,通过这个入口,任何人都可以给整个邮件列表发邮件。 没有调停者模式实现的话,那个人必须随时关注谁订阅了列表,而谁又没有订阅。通过实现调停者模式,系统可以从任何入口接受邮件,然后决定哪些接收者需要传送,而不需要发送者担心真正的接收名单。
Iterator迭代子 对象行为
目的:
允许得到一个聚集的元素,而不使用聚集本身的底层实现。
使用:
只需要得到聚集的元素,而不用知道整个聚集的内部表现。
需要对所有元素的多个或并行遍历。
需要对不同聚集的一个统一接口。
不同迭代子的实现细节有细微差异存在。
例子:
Java对迭代子模式的实现允许用户可以遍历不同类型的数据集合,而不用担心集合的底层实现。由于客户端只和迭代子接口打交道,各种集合可以留出余地定义一个适合他们的接口。有些允许存取所有集合的数据,有些禁止了某些功能,比如移除一些元素。
Memento备忘录 对象行为
目的:
允许存储和外化一个对象的内部状态以便以后可以恢复,而这一切并不破坏封装。
使用:
对象的内部状态必须存储以备以后某个时间恢复。
一个接口的内部状态不通过暴露实现就不能被使用。
封装边界必须被保留。
例子:
Undo功能可以通过备忘录模式得到很好实现。 在状态变化之前,通过序列化和反序列化一个对象的状态可以保留它的一个快照,如果用户需要undo操作的话,可以用这个快照恢复。
Observer观察者 对象行为
目的:
当系统中的主题对象状态发生变化时,让一个或多个观察者对象得到通知。
使用:
一个或多个对象的状态发生变化应该激发其他对象的行为。
需要广播(消息)的能力。
一个须知是,对象无视得到通知的代价。
例子:
这个模式几乎可以在任何界面环境中见到,当应用中出现按钮,文字,和其他变量,应用一般登记为这些控件的观察者(Listener),当一个用户激发一个事件,比如点击一个按钮, 控件遍历所有它登记的观察者(Observers)并对每个观察者一一发送通知。
Strategy策略 对象行为
目的:
定义一个系列的封装好的算法,可以互换以用来实现特定行为。
使用:
类之间的唯一区别是他们的行为。
需要一个算法的不同版本或演化。
算法存取或使用的数据不应该被暴露给调用方。
一个类的行为应该在运行时决定。
条件语句复杂而且难以维护,需要重构
例子:
当数据被导入到新系统的时候,不同的验证算法需要运行于数据之上。通过配置,导入和使用策略,决定哪些验证需要被运行的条件逻辑可以移除掉,(数据)导入可以和真实验证代码解耦。 这会允许我们导入数据时动态调用一个或多个策略。
State状态 对象行为
目的:
对象的情景绑定到它的行为上,允许对象根据内部状态表现不同行为。
使用:
对象的行为受它的状态影响。
把对象行为绑定到状态的条件复杂。
状态间需要显式转换。
例子:
一个邮件对象可以有不同状态,这些状态会影响对象的功能, 如果状态是“待发”,那么调用send()将会发出邮件,而调用recallMessage()会抛出错误或什么也不干,然而,如果状态时“已发出”状态,那么调用send()会抛出错误或什么也不干,而调用recallMessage()会尝试发送提醒通知给接受者。为避免方法中出现条件转移语句, 会使用多个状态对象处理相对应于特定状态的行为实现。邮件对象里的调用方法会委派给适当的状态对像来处理。
Template Method模板方法 类行为
目的:
标识识别一个算法的框架,允许实现类顶一个真正的行为。
使用:
需要算法的一个抽象的是实现。
所有子类的共同行为需要放置到共同类中。
父类应该统一调用所有子类中的行为。
大部分或所有子类需要实现一个共同的行为。
例子:
一个父类,InstantMessage,需要所有的方法去处理发送消息,但是要发送的序列化数据可能根据不同实现而不同,一个视频消息和一个文字消息需要不同的算法以使数据正确序列化。InstantMessage的子类可以产生他们各自的序列化实现方法。允许父类可以和他们一起作用,而无需知道各自的实现细节。
Visitor访问者 对象行为
这个图漏了ConcreteVisitor到Visitor的继承关系,读者心中有数。
目的:
允许运行时对一组对象进行一种或几种操作,而把对象结构和操作解耦。
使用:
一个对象结构须有多个不相关的操作作用其上。
一个对象结构不能改变,当时作用其上的操作可以变。
操作必须要作用在对象结构的具体类上。
可以接受对象结构暴露出来的内部状态和操作。
操作需要能够作用在继承统一的接口集合的对象结构之上。
例子:
对不同地区的一组发票计算税收需要很多不同的计算逻辑。实现访问者模式允许计算税收逻辑可以和发票详细项目解耦(我的理解是发票有很多不同的部分,每个部分需要不同的计税方式)。访问发票项目等级结构的计算逻辑可以和地区的合适税率解耦。改变地区就是简单改变一个不同的访问者具体类。
Bridge桥梁 对象结构
目的:
定义一个抽象化角色对象结构 和一个独立的实现化角色对象结构,抽象化角色的对象通过委派到实现化角色的对象来实现自己的功能,同时又能灵活的改变实现化角色对象的引用,因此达到解耦。
使用:
抽象化角色和实现化角色不应该在编译期绑定。
例子:
JVM, Java虚拟机有他自己的本地方法,抽象了视窗,系统日志,和字节码执行的使用,这些方法的具体实现委派到JVM运行的操作系统之上。当一个应用指示JVM去渲染窗口,他委派这个渲染的调用去JVM的具体实现,JVM知道怎么样和操作系统通信,以便渲染这个窗口。
Adapter适配器 类和对象结构
这个图中的Adapter接口是源角色,所以又叫Target。
目的:
通过这个具体适配器对象,使原本接口不匹配而无法一起工作的类能够一起工作。
使用:
一个要使用的类不满足接口需要。
复杂的条件把对象的行为绑定到他的状态。
状态需要显式转换。
例子:
一个账单应用需要人事应用的接口,以便交换雇员数据。但是他们都有自己对雇员数据接口的实现, 另一个要命的地方他们的社会安全保障号码使用不同的格式,通过在两个应用之间创建一个适配器类, 允许他们使用本地的对象来通信,也可以在这个过程中转换SSN格式。
Composite组合 对象结构
目的:
合成模式使客户端将单纯元素和复合元素同等看待,方便创建对象的树结构。
使用:
需要对象的等级结构时,
单纯对象和复合对象可以统一对待。
例子:
有时候购物车的显示内容是一个单独项目或多个项目的集合。将项目(item)实现为一个Composite接口的子类,我们就可以把集合项目和单纯项目统一对待,允许我们遍历对象树,调用每个项目的方法。通过调用一个节点的getCost方法,我们可以得到那个项目的金额,和所有子项目的金额。无论他们是单纯项目还是一组项目都可以统一被看待。
Decorator装饰 对象结构
目的:
允许动态包装对象以便修改它们现有的责任和行为。
使用:
对象责任和行为应该可以被动态地修改。
具体实现应该和责任和行为解耦。
用继承的方式去修改变得不实际和不可能。
特定功能不应驻留在对象层次的上层。
一大堆小对象包裹在具体实现周围是可以接受的。
例子:
很多公司利用装饰模式搭建邮件系统,当一个邮件从公司内部发送到外部邮件地址时,邮件服务器自动在原来的内容加上版权和机密信息,如果是发往内部地址,就不添加。这种装饰允许邮件本身不发生改变,添加额外信息的判断在运行时被执行。
Flyweight享元 对象结构
目的:
方便重用某些细粒度的对象,使对大量对象的使用更有效率。
使用:
很多相同的对象被使用而且存储代价比较高。
每个对象的主要状态可以外部存放。
有些共享对象可以代替很多不共享的对象。
每个对象的标识不重要。
例子:
系统允许用户定义他们自己的应用流程和布局经常需要跟踪记录很多数量的变量,页面和其他项目,而这些东西几乎没有差别,通过把这些东西做成享元对象,所有对象的实例可以共享内部状态,而把外部状态分开存放。内部状态将存储共享属性,比如一个对话框的视觉效果,可以装多少长度的数据,暴露出什么样的事件,外部状态会存储不共享的属性,比如项目的位置,怎样对用户的点击做出反应,怎么样处理事件。
Facade门面 对象结构
目的:
为一个子系统里面的一组接口提供一个单独的接口
使用:
需要一个简单的接口提供对一个复杂系统的访问
在系统实现和客户之间存在过多的依赖
系统和子系统需要被分层
例子:
通过Web服务暴露一组功能,客户代码只需要关心通过web服务暴露给他们的一个简单接口,而不是隐藏在web服务层后面存在的可能有的复杂关系。用新数据通过一个单独的web服务调用去更新系统可能涉及和几个数据库和系统的通信,然而这一切细节门面模式隐藏起来。
Proxy代理 对象结构
目的:
允许对象层的存取控制,像一个Pass through entity或placeholder object。
使用:
被代表的对象存在于系统的外部。
对象可以在需要时创建。
对真正要访问的对象提供存储控制。
当一个对象被存取时需要增加功能。
例子:
账本应用提供一个方式顾客可以用他们的银行对账单和银行记录进行对账。 使很多过程自动化,和第三方通信的真正的操作十分耗费资源,应该被限制执行次数。通过使用代理来表示通信对象,我们可以限制通信时间和间隔,更进一步的是,我们可以把复杂的通信对象的初始化操作隐藏在代理类里面,是调用代码和是实现细节解耦。
Abstract Factory抽象工厂 对象创建
目的:
提供一个接口,为了创建某些特定对象,把创建调用委派给某些具体创建类。
使用:
对象的创建应独立于是使用它们的系统。
系统应该能够使用多个对象族。
一个族的对象应该被集中使用。
程序库发布的时候不会暴露出其实现细节。
具体类应该和客户端解耦。
例子:
邮件编辑器应该允许操作多种格式,包括纯文字,富格式文字,和HTML。 根据被使用的格式,不同的对象需要被创建。如果邮件是纯文字,那么主体对象表示的就是纯文字和一个附件对象只是把附件简单编码成Base64。如果邮件是HTML,那么主体对象表示HTML 编码文字,附件对象允许inline表示和标准附件。通过利用抽象工厂来创建,我们可以保证合适的对象族根据邮件的风格来创建。
Factory Method工厂方法 对象创建
目的:
暴露一个方法来创建对象,允许子类来控制真正的创建过程。
使用:
一个类不知道他要创建的是什么对象。
子类可以指定需要创建的对象。
父类希望将创建的操作放在它的子类。
例子:
很多应用有一种user 和 group结构来做登录,当应用需要创建一个user,它将通常把创建的过程委派给多个工厂方法子类实现。父类User对象将处理每种User的大部分操作,但是子类将定义工厂方法处理不同种类的User创建的差别。系统可能有AdminUser和StandardUser对象,每一个继承了User,AdminUser对象执行某些任务保证使用权,而StandardUser却要限制使用权。
Builder建造者 对象创建
目的:
允许根据一些容易替换的算法动态创建对象。
使用:
对象创建算法应该和系统解耦。
需要创建算法的多种表示。
添加新的创建行为而不改变主要代码。
需要创建过程的运行时控制。
例子:
一个文件传输应用应该使用很多不同的协议来发送文件,真正的传输对象要根据选择的传输协议。通过使用一个Builder,我们可以决定使用正确的Builder来实例化正确的对象。 如果设置里是FTP那么FTP Builder将被使用。
Prototype原型 对象创建
目的:
根据一个已经存在的对象作为模板通过克隆来创建对象。
使用:
组合,创建和对象的表示应该和系统解耦。
要创建的类在运行时才决定。
一个对象内部存在有限数目的状态。
对象或需要的对象结构需要一致或非常接近其他已经存在的对象和对象结构。
一开始对每个对象的创建是一个很耗费的操作。
例子:
价格处理引擎常常需要寻找很多不同的配置参数,是初始化这个引擎来变得相对昂贵,当有几个这样的引擎实例需要初始化,使用多线程来导入数据。很多引擎的初始化耗费就会很高。通过使用原型模式,我们可以保证只有一个引擎的拷贝需要初始化,然后克隆这个实例来复制它。附加的好处是克隆可以流程化。
Singleton单例 对象创建
目的:
保证系统里只有一个类的实例。
使用:
只需要类的一个实例。
必须控制使用一个单独对象。
例子:
很多语言提供系统或环境对象,允许语言和本地操作系统交互。因为物理上在一个操作系统上运行的应用,只需要一个系统对象。单例模式被语言运行时来保证只有一个系统对象的拷贝被创建,只有合适的进程才可以访问它。