本文采用谷歌翻译器(Google Translator)翻译:建议先看英文再看中文。
第1节关于设计模式
这个Design Patterns refcard提供了对原始的23 Gang of Four设计模式的快速参考,如“Design Patterns:Elements of Reusable Object-Oriented Software”一书中所列。 每个模式包括类图,解释,使用信息和一个真实世界的例子。
- 创建模式:用于构造对象,使得它们可以与其实现系统解耦。
- 结构模式:用于在许多不同对象之间形成大对象结构。
- 行为模式:用于管理对象之间的算法,关系和职责。
- 对象范围:更新可在运行时更改的对象关系。
- 类范围:增加了类关系,可以在编译时更改。
C | 抽象工厂 | S | 装饰 | C | 原型 |
S | 适配器 | S | 正面 | S | 代理 |
S | 桥 | C | 工厂方法 | B | 观察员 |
C | 生成器 | S | 轻量级 | C | 单身 |
B | 责任链 | B | 翻译 | B | 州 |
B | 命令 | B | 调解员 | B | 模板方法 |
S | 综合 | B | 纪念 | B | 游客 |
责任链对象行为
目的
通过将接收对象链接在一起,使多个对象有机会处理请求。
使用时间
- 多个对象可以处理请求,并且处理程序不必是特定对象。
- 一组对象应该能够处理具有在运行时确定的处理程序的请求。
- 未处理的请求是可接受的潜在结果。
例
在某些语言中的异常处理实现了这种模式。 当一个方法抛出一个异常时,运行时会检查该方法是否有一个处理异常的机制,或者是否应该将它传递给调用堆栈。 当向上传递调用堆栈时,该过程重复,直到遇到处理异常的代码或直到不再有请求的父对象。
命令对象行为
目的
封装请求,允许将其视为对象。 这允许在传统的基于对象的关系(例如排队和回调)中处理请求。
使用时间
- 您需要回调功能。
- 请求需要在变量时间或变量顺序处理。
- 需要请求的历史。
- 调用者应该与处理调用的对象分离。
例
作业队列被广泛用于促进算法的异步处理。 通过利用命令模式,要执行的功能可以被给予用于处理的作业队列,而不需要队列知道它正在调用的实际实现。 入队的命令对象在队列期望的接口的范围内实现其特定算法。
译员类行为
目的
定义语法的表示,以及理解和操作语法的机制。
使用时间
- 有解释的语法,可以表示为大的语法树。
- 语法很简单。
- 效率并不重要。
- 需要从基础表达式中去耦语法。
例
基于文本的冒险,在20世纪80年代非常流行,提供了一个很好的例子。 许多人有简单的命令,如“下台”,允许游戏的遍历。 这些命令可以嵌套以使其改变其含义。 例如,“进入”将导致与“上升”不同的结果。 通过基于命令和限定符(非终端和终端表达式)创建命令的层次结构,应用可以容易地将许多命令变体映射到相关的动作树。
迭代器对象行为
目的
允许访问聚合对象的元素,而不允许访问其底层表示。
使用时间
- 需要访问元素而无需访问整个表示。
- 需要元素的多个或并发遍历。
- 需要用于遍历的统一接口。
- 各种迭代器的实现细节之间存在细微的差异。
例
迭代器模式的Java实现允许用户遍历各种类型的数据集,而不用担心集合的底层实现。 由于客户端只需与迭代器接口交互,因此集合可以为自己定义合适的迭代器。 有些将允许完全访问底层数据集,而其他人可能会限制某些功能,例如删除项目。
调解者对象行为
目的
允许通过封装不同的对象集相互交互和通信的方式松散耦合。 允许每个对象集的操作彼此独立地变化。
使用时间
- 对象集之间的通信是明确定义和复杂的。
- 存在太多的关系并且需要公共控制或通信点。
例
邮件列表软件跟踪谁登记到邮件列表,并提供单个访问点,任何一个人都可以通过该点访问整个列表。 如果没有调解器实现,想要向群组发送消息的人将不得不经常跟踪谁签署了谁,谁不是。 通过实现中介器模式,系统能够从任何点接收消息,然后确定哪些收件人将消息转发到其上,而消息的发送者不必关心实际的接收者列表。
Memento对象行为
目的
允许捕获和外部化对象的内部状态,以便以后可以恢复,而不会违反封装。
使用时间
- 必须保存对象的内部状态,并在以后恢复。
- 内部状态不能通过接口暴露而不暴露实现。
- 必须保留封装边界。
例
撤消功能可以很好地使用纪念品模式实现。 通过在更改发生之前序列化和反序列化对象的状态,我们可以保留它的快照,稍后可以在用户选择撤消操作时恢复该快照。
观察者对象行为
目的
允许向一个或多个对象通知系统中其他对象的状态更改。
使用时间
- 一个或多个对象中的状态更改应触发其他对象中的行为
- 需要广播功能。
- 有一个理解存在,对象将盲目通知的费用。
例
这种模式可以在几乎每个GUI环境中找到。 当按钮,文本和其他字段放置在应用程序中时,应用程序通常注册为这些控件的侦听器。 当用户触发事件(例如点击按钮)时,控件会遍历其注册的观察者并向每个观察者发送通知。
状态对象行为
目的
将对象环境与其行为绑定,允许对象基于其内部状态以不同的方式运行。
使用时间
- 对象的行为应该受其状态的影响。
- 复杂条件将对象行为绑定到其状态。
- 状态之间的转换需要是显式的。
例
电子邮件对象可以具有各种状态,所有这些状态将改变对象如何处理不同的功能。 如果状态是“未发送”,则send()的调用将发送消息,而recallMessage()的调用将抛出错误或什么也不做。 然而,如果状态是“sent”,则send()的调用将抛出一个错误或什么也不做,而调用recallMessage()将尝试向收件人发送一个调用通知。 为了避免大多数或所有方法中的条件语句,将存在处理关于其特定状态的实现的多个状态对象。 然后,将电子邮件对象内的调用委派给适当的状态对象进行处理。
战略对象行为
目的
定义一组封装的算法,可以交换执行特定的行为。
使用时间
- 许多相关类之间的唯一区别是它们的行为。
- 需要算法的多个版本或变体。
- 算法访问或利用调用代码不应该暴露的数据。
- 类的行为应该在运行时定义。
- 条件语句复杂且难以维护。
例
当将数据导入新系统时,可以基于数据集运行不同的验证算法。 通过配置导入以利用策略,条件逻辑来确定要运行的验证集合可以被移除,并且导入可以与实际的验证代码去耦合。 这将允许我们在导入期间动态调用一个或多个策略。
模板方法类行为
目的
标识算法的框架,允许实现类来定义实际行为。
使用时间
- 需要一个算法的单一抽象实现。
- 子类之间的常见行为应该本地化到公共类。
- 父类应该能够统一调用其子类中的行为。
- 大多数或所有子类需要实现行为。
例
父类InstantMessage可能具有处理发送消息所需的所有方法。 然而,要发送的数据的实际串行化可以根据实现而变化。 视频消息和纯文本消息将需要不同的算法以便正确地串行化数据。 InstantMessage的子类可以提供自己的序列化方法的实现,允许父类在不了解其实现细节的情况下使用它们。
访客对象行为
目的
允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。
使用时间
- 对象结构必须对其执行许多不相关的操作。
- 对象结构不能改变,但对其执行的操作可以。
- 必须对对象结构的具体类执行操作。
- 暴露对象结构的内部状态或操作是可接受的。
- 操作应该能够对实现相同接口集的多个对象结构进行操作。
例
在不同区域计算发票集合上的税将需要许多不同的计算逻辑变化。 实施访问者允许将逻辑与发票和订单项解耦。 这允许通过计算代码访问项目的层次结构,然后可以对区域应用适当的速率。 更改区域就像替换不同的访问者一样简单。
适配器类和对象结构
目的
允许具有不同接口的类通过创建可以通信和交互的公共对象来协同工作。
使用时间
- 要使用的类不满足接口要求。
- 复杂条件将对象行为绑定到其状态。
- 状态之间的转换需要是显式的。
例
计费应用程序需要与HR应用程序接口以便交换员工数据,但每个都有自己的Employee对象的接口和实现。 此外,每个系统以不同的格式存储SSN。 通过创建适配器,我们可以在两个应用程序之间创建一个通用接口,允许它们使用其本机对象进行通信,并能够在过程中转换SSN格式。
桥梁对象结构
目的
独立于实现对象结构定义抽象对象结构,以限制耦合。
使用时间
- 抽象和实现不应在编译时绑定。
- 抽象和实现应该是可独立扩展的。
- 抽象的实现中的更改应该对客户端没有影响。
- 实施细节应该隐藏在客户端。
例
Java虚拟机(JVM)有自己的本地函数集,它们提取了窗口,系统日志和字节码执行的使用,但是这些函数的实际实现被委托给JVM运行的操作系统。 当应用程序指示JVM渲染窗口时,它将渲染调用委派给知道如何与操作系统通信以渲染窗口的JVM的具体实现。
复合对象结构
目的
便于创建对象层次结构,其中每个对象可以通过同一接口单独处理或作为一组嵌套对象处理。
使用时间
- 需要对象的分层表示。
- 对象的对象和组成应进行统一处理。
例
有时,购物车中显示的信息是单个项目的产品,而其他时间则是多个项目的聚合。 通过将项目实现为复合,我们可以以相同的方式处理聚合和项目,使我们能够简单地遍历树并调用每个项目的功能。 通过在任何给定节点上调用getCost()方法,我们将得到该项目的成本加上所有子项目的成本,允许项目被统一处理,不管它们是单个项目还是一组项目。
装饰对象结构
目的
允许对象的动态包装,以便修改其现有的职责和行为。
使用时间
- 对象的责任和行为应该是可动态修改的。
- 具体实现应该与责任和行为分离。
- 子类化以实现修改是不切实际或不可能的。
- 特定功能不应位于对象层次结构中的较高位置。
- 围绕具体实现的许多小对象是可以接受的。
例
许多企业设置他们的邮件系统以利用装饰器。 当邮件从公司中的某人发送到外部地址时,邮件服务器将使用版权和机密信息装饰原始邮件。 只要消息保持内部,则不附加信息。 这种装饰允许消息本身保持不变,直到做出运行时决定用附加信息来包装消息。
门面对象结构
目的
为系统中的一组接口提供单个接口。
使用时间
- 需要一个简单的接口来提供对复杂系统的访问。
- 系统实现和客户端之间有很多依赖关系。
- 系统和子系统应该分层。
例
通过Web服务暴露一组功能,客户端代码只需要担心暴露给它们的简单接口,而不是担心Web服务层后面可能存在或可能不存在的复杂关系。 使用新数据更新系统的单个Web服务调用实际上可能涉及与多个数据库和系统的通信,然而由于正面模式的实现,这个细节被隐藏。
飞重对象结构
目的
促进许多细粒度对象的重用,使大量对象的利用更有效率。
使用时间
- 使用许多类似的对象,存储成本高。
- 每个对象的大多数状态可以是外部的。
- 几个共享对象可以替换许多非共享对象。
- 每个对象的身份无关紧要。
例
允许用户定义其自己的应用程序流和布局的系统通常需要跟踪大量字段,页面和彼此几乎相同的其他项目。 通过将这些项目制成飞重,每个对象的所有实例可以共享内在状态,同时保持外在状态分离。 内部状态将存储共享属性,例如文本框的外观,它可以容纳多少数据以及它暴露的事件。 外在状态将存储非共享属性,例如项目所属,如何对用户点击做出反应以及如何处理事件。
代理对象结构
目的
通过充当传递实体或占位符对象来允许对象级访问控制。
使用时间
- 被表示的对象在系统外部。
- 对象需要根据需要创建。
- 需要对原始对象的访问控制。
- 访问对象时需要添加功能。
例
分类帐应用程序通常为用户提供一种方法,使银行对账单与其分类帐数据按需协调,从而自动执行大部分过程。 与第三方通信的实际操作是应该受到限制的相对昂贵的操作。 通过使用代理来表示通信对象,我们可以限制通信被调用的次数或间隔。 此外,我们可以在代理类中封装通信对象的复杂实例化,将调用代码与实现细节解耦。
抽象工厂对象创造
目的
提供一个接口,用于将创建调用委派给一个或多个具体类,以便传递特定对象。
使用时间
- 对象的创建应该独立于利用它们的系统。
- 系统应该能够使用多个对象族。
- 对象家庭必须一起使用。
- 库必须在不公开实施细节的情况下发布。
- 具体类应该从客户端解耦。
例
电子邮件编辑器将允许以多种格式编辑,包括纯文本,富文本和HTML根据所使用的格式,将需要创建不同的对象。 如果消息是纯文本,那么可能有一个body对象只表示纯文本和附件对象,只是将附件加密到Base64。 如果消息是HTML,那么主体对象将表示HTML编码文本,附件对象将允许内联表示和标准附件。 通过利用抽象工厂进行创建,我们可以确保基于正在发送的电子邮件的样式创建适当的对象集。
构建器对象创建
目的
允许基于易于互换的算法动态创建对象。
使用时间
- 对象创建算法应该与系统分离。
- 需要创建算法的多重表示。
- 添加新的创建功能而不改变核心代码是必要的。
- 需要对创建过程进行运行时控制。
例
文件传输应用程序可能使用许多不同的协议来发送文件,并且将被创建的实际传输对象将直接依赖于所选择的协议。 使用构建器,我们可以确定用于实例化正确对象的正确构建器。 如果设置为FTP,则创建对象时将使用FTP构建器。
工厂方法对象创建
目的
公开一个创建对象的方法,允许子类控制实际的创建过程。
使用时间
- 类不知道需要创建什么类。
- 子类可以指定应该创建什么对象。
- 父类希望将创建推迟到它们的子类。
例
许多应用程序具有某种形式的用户和组安全性结构。 当应用程序需要创建用户时,它通常会将用户的创建委托给多个用户实现。 父用户对象将处理每个用户的大多数操作,但子类将定义处理每种类型的用户的创建中的区别的工厂方法。 系统可以具有AdminUser和StandardUser对象,每个对象扩展了User对象。 AdminUser对象可以执行一些额外的任务以确保访问,而StandardUser可以执行相同的操作来限制访问。
原型对象创建
目的
通过克隆基于现有对象的模板创建对象。
使用时间
- 对象的组成,创建和表示应与系统分离。
- 要创建的类在运行时指定。
- 有限数量的状态组合存在于对象中。
- 需要与其他现有对象或对象结构相同或非常类似的对象或对象结构。
- 每个对象的初始创建是一个昂贵的操作。
例
速率处理引擎通常需要查找许多不同的配置值,使得引擎的初始化是相对昂贵的过程。 当需要引擎的多个实例时,例如以多线程方式导入数据,初始化许多引擎的费用很高。 通过使用原型模式,我们可以确保只需要初始化引擎的一个副本,然后简单地克隆引擎以创建已经初始化的对象的副本。 这样做的额外好处是可以简化克隆,仅包括其情况的相关数据。
单例对象创建
目的
确保在系统中只允许一个类的一个实例。
使用时间
- 只需要一个类的一个实例。
- 需要对单个对象的受控访问。
例
大多数语言提供某种系统或环境对象,允许语言与本机操作系统交互。 由于应用程序仅在一个操作系统上物理运行,因此只需要此系统对象的单个实例。 单例模式将由语言运行时实现,以确保仅创建系统对象的单个副本,并且确保仅允许适当的进程访问它。