23种设计模式
工厂方法
Factory Method(工厂方法) clip_image001 意图: 定义一个用于创建对象的接口。让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。 适用性: 当一个类不知道它所必须创建的对象的类的时候。
当一个类希望由它的子类来指定它所创建的对象的时候。 当类将创建对象的职责托付给多个帮助子类中的某一个,而且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
抽象工厂
<pre name="code" class="html">意图: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们详细的类。 适用性: 一个系统要独立于它的产品的创建、组合和表示时。一个系统要由多个产品系列中的一个来配置时。 当你要强调一系列相关的产品对象的设计以便进行联合使用时。 当你提供一个产品类库,而仅仅想显示它们的接口而不是实现时。
建造者
意图: 将一个复杂对象的构建与它的表示分离,使得相同的构建过程能够创建不同的表示。 适用性: 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。当构造过程必须同意被构造的对象有不同的表示时。
原型
意图: 用原型实例指定创建对象的种类,而且通过拷贝这些原型创建新的对象。 适用性: 当要实例化的类是在执行时刻指定时,比如,通过动态装载;或者 为了避免创建一个与产品类层次平行的工厂类层次时。或者 当一个类的实例仅仅能有几个不同状态组合中的一种时。建立对应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
单例
意图: 保证一个类仅有一个实例,并提供一个訪问它的全局訪问点。 适用性: 当类仅仅能有一个实例并且客户能够从一个众所周知的訪问点訪问它时。 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
适配器
意图: 将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本因为接口不兼容而不能一起工作的那些类能够一起工作。
适用性: 你想使用一个已经存在的类,而它的接口不符合你的需求。
你想创建一个能够复用的类,该类能够与其它不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。 (仅适用于对象Adapter )你想使用一些已经存在的子类,可是不可能对每个都进行子类化以匹配它们的接口。对象适配器能够适配它的父类接口。
桥接
意图: 将抽象部分与它的实现部分分离。使它们都能够独立地变化。 适用性: 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。比如这样的情况可能是由于,在程序执行时刻实现部分应能够被选择或者切换。
类的抽象以及它的实现都应该能够通过生成子类的方法加以扩充。
这时Bridge 模式使你能够对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
对一个抽象的实现部分的改动应对客户不产生影响,即客户的代码不必又一次编译。 (C++)你想对客户全然隐藏抽象的实现部分。
在C++中。类的表示在类接口中是可见的。 有很多类要生成。
这样一种类层次结构说明你必须将一个对象分解成两个部分。
Rumbaugh 称这样的类层次结构为“嵌套的普化”(nested generalizations )。 你想在多个对象间共享实现(可能使用引用计数)。但同一时候要求客户并不知道这一点。一个简单的样例便是Coplien 的String 类[ Cop92 ],在这个类中多个对象能够共享同一个字符串表示(StringRep )。
组合
意图: 将对象组合成树形结构以表示“部分-总体”的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。 适用性: 你想表示对象的部分-总体层次结构。 你希望用户忽略组合对象与单个对象的不同。用户将统一地使用组合结构中的全部对象。
装饰
意图: 动态地给一个对象加入一些额外的职责。就添加功能来说,Decorator 模式相比生成子类更为灵活。 适用性: 在不影响其它对象的情况下,以动态、透明的方式给单个对象加入职责。 处理那些能够撤消的职责。 当不能採用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展。为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
还有一种情况可能是由于类定义被隐藏,或类定义不能用于生成子类。
外观
意图: 为子系统中的一组接口提供一个一致的界面。Facade模式定义了一个高层接口,这个接口使得这一子系统更加easy使用。 适用性: 当你要为一个复杂子系统提供一个简单接口时。子系统往往由于不断演化而变得越来越复杂。大多数模式使用时都会产生很多其它更小的类。这使得子系统更具可重用性,也更easy对子系统进行定制,但这也给那些不须要定制子系统的用户带来一些使用上的困难。Facade 能够提供一个简单的缺省视图。这一视图对大多数用户来说已经足够,而那些须要很多其它的可定制性的用户能够越过facade层。 客户程序与抽象类的实现部分之间存在着非常大的依赖性。引入facade 将这个子系统与客户以及其它的子系统分离,能够提高子系统的独立性和可移植性。 当你须要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。
假设子系统之间是相互依赖的,你能够让它们仅通过facade进行通讯。从而简化了它们之间的依赖关系。
享元
意图: 运用共享技术有效地支持大量细粒度的对象。适用性: 一个应用程序使用了大量的对象。 全然因为使用大量的对象,造成非常大的存储开销。
对象的大多数状态都可变为外部状态。 假设删除对象的外部状态,那么能够用相对较少的共享对象代替非常多组对象。
应用程序不依赖于对象标识。因为Flyweight 对象能够被共享,对于概念上明显有别的对象,标识測试将返回真值。
代理
意图: 为其它对象提供一种代理以控制对这个对象的訪问。适用性: 在须要用比較通用和复杂的对象指针代替简单的指针的时候。使用Proxy模式。以下是一 些能够使用Proxy 模式常见情况: 1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用NXProxy 类实现了这一目的。
Coplien[Cop92] 称这样的代理为“大使” (Ambassador )。 2 )虚代理(Virtual Proxy )依据须要创建开销非常大的对象。
在动机一节描写叙述的ImageProxy 就是这样一种代理的样例。
3) 保护代理(Protection Proxy )控制对原始对象的訪问。保护代理用于对象应该有不同 的訪问权限的时候。比如。在Choices 操作系统[ CIRM93]中KemelProxies为操作系统对象提供 了訪问保护。 4 )智能指引(Smart Reference )代替了简单的指针。它在訪问对象时运行一些附加操作。
它的典型用途包含: 对指向实际对象的引用计数。这样当该对象没有引用时。能够自己主动释放它(也称为SmartPointers[Ede92 ] )。 当第一次引用一个持久对象时。将它装入内存。
在訪问一个实际对象前。检查是否已经锁定了它。以确保其它对象不能改变它。
解释器
意图: 给定一个语言,定义它的文法的一种表示。并定义一个解释器。这个解释器使用该表示来解释语言中的句子。适用性: 当有一个语言须要解释运行, 并且你可将该语言中的句子表示为一个抽象语法树时。可使用解释器模式。而当存在下面情况时该模式效果最好: 该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。
此时语法分析程序生成器这种工具是更好的选择。它们无需构建抽象语法树就可以解释表达式, 这样能够节省空间并且还可能节省时间。
效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成还有一种形式。比如,正則表達式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是实用的。
模板
意图: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类能够不改变一个算法的结构就可以重定义该算法的某些特定步骤。 适用性: 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码反复。这是Opdyke 和Johnson 所描写叙述过的“重分解以一般化”的一个非常好的样例[ OJ93 ]。首先识别现有代码中的不同之处。而且将不同之处分离为新的操作。最后。用一个调用这些新的操作的模板方法来替换这些不同的代码。 控制子类扩展。模板方法仅仅在特定点调用“hook ”操作(參见效果一节),这样就仅仅同意在这些点进行扩展。
责任链
意图: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 适用性: 有多个的对象能够处理一个请求,哪个对象处理该请求执行时刻自己主动确定。
你想在不明白指定接收者的情况下。向多个对象中的一个提交一个请求。 可处理一个请求的对象集合应被动态指定。
命令
意图: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行參数化。对请求排队或记录请求日志,以及支持可撤消的操作。适用性: 抽象出待运行的动作以參数化某对象,你可用过程语言中的回调(call back)函数表达这样的參数化机制。
所谓回调函数是指函数先在某处注冊,而它将在稍后某个须要的时候被调用。
Command 模式是回调机制的一个面向对象的替代品。 在不同的时刻指定、排列和运行请求。一个Command对象能够有一个与初始请求无关的生存期。假设一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给还有一个不同的进程并在那儿实现该请求。 支持取消操作。Command的Excute 操作可在实施操作前将状态存储起来。在取消操作时这个状态用来消除该操作的影响。
Command 接口必须加入一个Unexecute操作。该操作取消上一次Execute调用的效果。运行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。 支持改动日志,这样当系统崩溃时。这些改动能够被重做一遍。
在Command接口中加入装载操作和存储操作,能够用来保持变动的一个一致的改动日志。
从崩溃中恢复的过程包含从磁盘中又一次读入记录下来的命令并用Execute操作又一次运行它们。 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中非经常见。
一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你能够用同一种方式调用全部的事务。
同一时候使用该模式也易于加入新事务以扩展系统。
中介
意图: 用一个中介对象来封装一系列的对象交互。中介者使各对象不须要显式地相互引用。从而使其耦合松散,并且能够独立地改变它们之间的交互。 适用性: 一组对象以定义良好可是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。 一个对象引用其它非常多对象并且直接与这些对象通信,导致难以复用该对象。 想定制一个分布在多个类中的行为。而又不想生成太多的子类。
备忘录
意图: 在不破坏封装性的前提下。捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 适用性: 必须保存一个对象在某一个时刻的(部分)状态, 这样以后须要时它才干恢复到先前的状态。 假设一个用接口来让其他对象直接得到这些状态。将会暴露对象的实现细节并破坏对象的封装性。
观察者
意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 全部依赖于它的对象都得到通知并被自己主动更新。适用性: 当一个抽象模型有两个方面, 当中一个方面依赖于还有一方面。
将这二者封装在独立的对象中以使它们能够各自独立地改变和复用。
当对一个对象的改变须要同一时候改变其他对象, 而不知道详细有多少对象有待改变。 当一个对象必须通知其他对象。而它又不能假定其他对象是谁。换言之, 你不希望这些对象是紧密耦合的。
备忘录
意图: 同意一个对象在其内部状态改变时改变它的行为。对象看起来似乎改动了它的类。适用性: 一个对象的行为取决于它的状态, 而且它必须在执行时刻依据状态改变它的行为。
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
这个状态通经常使用一个或多个枚举常量表示。通常, 有多个操作包括这一同样的条件结构。State模式将每个条件分支放入一个独立的类中。这使得你能够依据对象自身的情况将对象的状态作为一个对象,这一对象能够不依赖于其它对象而独立变化。
状态
<p><strong>意图:</strong></p><p>同意一个对象在其内部状态改变时改变它的行为。对象看起来似乎改动了它的类。</p><p><strong>适用性:</strong></p><p>一个对象的行为取决于它的状态,而且它必须在执行时刻依据状态改变它的行为。
</p>一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通经常使用一个或多个枚举常量表示。
通常,有多个操作包括这一同样的条件结构。State模式将每个条件分支放入一个独立的类中。
这使得你能够依据对象自身的情况将对象的状态作为一个对象,这一对象能够不依赖于其它对象而独立变化。
策略
意图: 定义一系列的算法,把它们一个个封装起来, 而且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。适用性: 很多相关的类不过行为有异。
“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
须要使用一个算法的不同变体。
比如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时[H087] ,能够使用策略模式。 算法使用客户不应该知道的数据。
可使用策略模式以避免暴露复杂的、与算法相关的数据结构。 一个类定义了多种行为, 而且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以取代这些条件语句。
訪问者
意图: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类能够不改变一个算法的结构就可以重定义该算法的某些特定步骤。 适用性: 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码反复。这是Opdyke和Johnson所描写叙述过的“重分解以一般化”的一个非常好的样例[OJ93]。
首先识别现有代码中的不同之处,而且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。 控制子类扩展。
模板方法仅仅在特定点调用“hook ”操作(參见效果一节),这样就仅仅同意在这些点进行扩展。