对象创建-Object Creation
1.原型 -Prototype
定义
原型模式是一种简单的设计模式,客户端知道抽象Prototype类。在运行时,抽象Prototype子类的任何对象都可以按照客户端的意图来进行复制。因此无需手工创建就可以创造同一类型的多个实例。
类图
使用场景
在以下情形,会考虑使用原型模式:
- 需要创建的对象应独立于其类型与创建方式
- 要实例化的类是在运行时决定的
- 不想要一个与产品层次相对应的工厂层次(也就是工厂类不需要与产品类一一对应)
- 不同类实例间的差异仅是状态的若干组合。因此复制相应数量的原型比手工实例化更加方便。
- 类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易
使用原型模式的常见误解是“原型对象应该是一种象征性对象从来不切实用”,从功能的角度上来看,不管什么对象,只要复制自身比手工实例化要好,都可以是原型对象.在以下两种特别常见的情形,我们会考虑使用此Prototype模式:
- 有很多相关的类,其行为略有不同,而且主要差异在于内部属性,如名称,图像等
- 需要使用组合(树形)对象作为其他东西的基础,例如,使用组合对象作为组件来构建另一个组合对象.
现实世界中还有许多状况应该应用这一模式.使用设计模式更像艺术行为而非科学行为.打破常规,用于创新,更聪明地工作.
此模式的最低限度是生成对象的真实副本(对象复制,深/浅复制),以作为同一环境下其他相关事物的基础(原型)
2.工厂方法-Factory Method
定义
工厂方法也称为虚构造器(virtual constructor).它适用的情形是"一个类无法预期需要生成那个类的对象,想让其子类来指定所生成的对象".所以工厂方法可以使得一个类的实例化延迟到其子类.
类图
使用场景
在以下情形,会考虑使用工厂方法模式:
- 编译时无法准确定预期要创建的对象的类
- 类想让其子类决定在运行时创建什么
- 类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部化(原文:A class has some helper classes as its subclasses and you want to localize the knowledge of which one to return)
使用这一模式的最低限度是,工厂方法能给予类在变更返回哪一种对象这一点上更多的灵活性.
3.抽象工厂-Abstract Factory
定义
抽象工厂模式提供一个固定的接口,用于创建一系列有关联或相依存的对象,而不必指定其具体类或其创建的细节.客户端与工厂得到的具体对象之间没有耦合.
类图
通过上面类图所示, Client只知道AbstractFactory和AbstractProduct.每个工厂类中,结构与实际操作的细节按黑箱对待.甚至产品也不知道谁将负责创建它们.只有具体的工厂知道为客户端创建什么,如何创建.这个模式有一个有趣点是,很多时候它都是用
工厂方法模式来实现.工厂方法把实际的创建过程推迟到重载它的子类中.在类图中,方法createProductA和createProductB是工厂方法.最初的抽象方法什么也不知道.这种抽象非常通用,广泛用于任何需要抽象创建过程的场合.
抽象工厂模式常与原型模式,单例模式,享元模式等其他一些设计模式一起使用.
使用场景
抽象工厂与工厂方法模式在许多方面都非常相似.很多人常常搞不清应该在什么时候用哪个. 两个模式都用于相同的目的:创建对象而不让客户端知晓返回了什么确切的具体对象.下表为抽象工厂模式与工厂方法模式的对比.
抽象工厂 | 工厂方法 |
---|---|
通过对象组合创建抽象产品 | 通过类继承创建抽象产品 |
创建多系列产品 | 创建一种产品 |
必须修改父类的接口才能支持新的产品 | 子类化创建者并重载工厂方法以创建新产品 |
4.生成器-Builder
定义
有时,构建某些对象有多种不同的方式.如果这些逻辑包含在构建这些对象的类的单一方法中,构建的逻辑会非常荒唐(列如,针对各种构建需求的一大片嵌套if-else或者switch-case语句),如果能够把构建过程分解为"客户-指导者-生成器"(client-director-builder)的关系,那么过程将更容易管理与复用.针对此类关系的设计模式称为生成器.
生成器模式: 将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现
类图
- 在生成器模式中,除了要生成的产品product和客户client之外,还要两个关键角色就是:指导者Director,和生成器Builder.
- 这种模式将产品的创建过程(需要一个什么样(what)的产品,如何(how)创建这样的产品)中的how和what分离了
- Director知道需要什么样(what)的产品, Builder生成器知道如何(how)创建产品
- Builder是个抽象接口,声明了一个builderPart方法,该builder方法由ConcretBuilder实现,以构造实际产品(Product).
- ConcretBuilder有个getResult方法,向客户端返回构造完毕的Product.
- Director定义了一个construct方法,命令Builder的实例去buildPart.
- Director和Builder形成了一种聚合关系,这意味着Builder是一个组成部分,与Director结合,以使整个模式运转,但Director并不负责Builder的生存期.
使用场景
在以下情形,会考虑使用生成器模式:
- 需要创建涉及各种部件的复杂对象.创建对象的算法应该独立于部件的装配方式.常见列子是构建组合对象
- 构建过程需要以不同的方式(列如,部件或变现的不同组合)构建对象.
生成器和抽象工厂的对比
抽象工厂和生成器模式很相似,然后两者还是有很大的区别的:
- 生成器关注的是分布创建复杂对象,很多时候同一类型的对象可以以不同的方式创建.
- 抽象工厂的重点在于创建简单或复杂产品的套件
- 生成器在多步创建过程的最后一步放回产品,而抽象工厂则立即返回产品
表格:生成器模式和抽象工厂模式的主要差异
生成器 | 抽象工厂 |
---|---|
构建复杂对象 | 构建简单或复杂对象 |
以多个步骤创建对象 | 以单一步骤创建对象 |
以多种方式构建对象 | 以单一方式构建对象 |
在构建过程的最后一步返回产品 | 立刻返回产品 |
专注一个特定产品 | 强调一套产品 |
生成器模式的整体思想是:分离"什么"和"如何",使得aDirector能把同一个"什么"(规格)应用到不同的aBuilder,而它懂得"如何"按照给定的规格构建自己特定的产品.
5.单例-Singleton
定义
面向对象应用程序中的单例类(singleton class)总是返回自己的同一个实例. 它提供了对类的对象的所提供的资源的全局访问点,与这类设计相关的设计模式称为单例模式.
- 单例模式几乎是最简单的设计模式了
- 这一模式意图使得类的一个对象成为系统中的唯一实例
- 单例模式:保证一个类仅有个实例,并提供一个访问它的全局访问点
类图
使用场景
在以下情形,会考虑使用单例模式:
- 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法
- 这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码
接口适配-Interface Adaptation
6.适配器-Adapter
定义
- 适配器模式是用于连接两种不同种类的对象,使其毫无问题地协同工作.有时它也被称为"包装器(wrapper)"
- 思想很简单:适配器实现客户端所需要的某种接口的行为.同时,它又连接到另一个具有(完全)不同接口与行为的对象.
- 一边是客户端懂得如何使用的目标接口,另一边是客户端一无所知的被试配者,适配器站在两者之间,所以适配器的主要作用是把被试配者的行为传递给管道另一端的客户端
类图
按照实现适配器模式的两种方式分为两种
-
类适配器
- Adapter是一个Target类型,同时也是一个Adaptee类型.Adapter重载Target的request方法.
- Adapter没有重载Adaptee的specificRequest方法,而是在Adapter的request方法的实现中,调用超类的specificRequest方法.request方法在运行时向超类发送[super specificRequest]消息.super就是Adaptee,它在Adapter的request方法的作用域内,按照自己的方式执行specificRequesst方法.
- 只有当Target是协议而不是类时,类适配器才能够使用OC来实现.
-
对象适配器
- 与类适配器不同,对象适配器不继承被适配者,而是组合了一个对它的引用.所以Adapter和Adaptee之间的关系从"属于"变成了"包含".
- Adapter需要保持一个对Adapter的引用. 在request方法中,Adapter发送[adaptee specificRequest]消息给引用adaptee,以间接访问它的行为,然后实现客户端请求的其余部分
- 由于Adapter与Adaptee之间是一种"包含"的关系,用Adapter去适配Adaptee的子类也没有什么问题.
类适配和对象适配器的对比
类适配器与对象适配器是实现适配器模式的不同方式,但是达成的目的相同.设计中采用适配器模式时应该选择哪一种呢? 请看下表:类适配器和对象适配器的特征对比
类适配器 | 对象适配器 |
---|---|
只针对单一的具体Adaptee类,把Adaptee适配到Target | 可以适配多个Adaptee及其子类 |
易于重载Adaptee的行为,因为是通过直接的子类化进行适配 | 难以重载Adaptee的行为,需要借助于子类的对象而不是Adaptee本身 |
只有一个Adapter对象,无需额外的指针间接访问Adaptee | 需要额外的指针以间接访问Adaptee并适配其行为 |
提示,OC中委托模式(Delegate)属于对象适配器
使用场景
在以下情形,会考虑使用适配器模式:
- 已有类的接口与需求不匹配
- 想要一个可复用的类,该类能够同可能带有不兼容接口的其他类协作
- 需要适配一个类的几个不同子类, 可是让每个子类去子类化一个类适配器有不现实.那么可以使用对象适配(也叫委托)来适配其父类的接口
适配器模式:将一个类的接口转换成客户希望的另一个接口,适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作.
7.桥接-Bridge
定义
桥接模式的目的是把抽象层次结构从器实现中分离出来,使其能够独立变更.抽象层定义了供客户端使用的上层的抽象接口.实现层次结构定义了供抽象层次使用的底层接口.实现类的引用被封装于抽象层的实例中时,桥接就形成了.
类图
- Abstraction是定义了供客户端使用的上层抽象接口的父接口.它有一个对Implementor实例的引用.
- Implementor是定义了实现类的接口,这个接口不必和Abstraction的接口一致,其实两个接口可以相当的不同. Implementor的接口提供基本的操作,而Abstraction的上层操作基于这些基本操作
- 当客户端向Abstraction的实例发送operation消息时,这个方法向imp发送的operationImp消息.底下的实际ConcreteImplementator(A/B)将作出反应并接受任务.
作用
- 想要往系统中添加新的ConcreteImplementator时,所需要做的只是为Implementor创建一个新的实现类,响应operationImp消息并在其中任何具体的操作.不过,这对Abstraction方面不会有任何影响.
- 如果想修改Abstraction的接口或者创建更细化的Abstraction类,也能做到不影响桥接的另一头.
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化
使用场景
在以下情形,会考虑使用桥接模式:
- 不想在抽象与其实现之间形成固定的绑定关系(这样就能在运行时切换实现);
- 抽象及其实现都应可以通过子类化独立进行扩展
- 对抽象的实现进行修改不应该影响客户端代码
- 如果每个实现需要额外的子类以细化抽象,则说明有必要把他们分成两个部分
- 想在带有不同抽象接口的多个对象之间共享一个实现.
8.外观-Facade
定义
很多旧的面向对象应用程序中,可能有许多类分散于带有各种功能的系统之中.要把这些类用于某个功能,需要知道全部细节才能在一组算法中使用它们.如果从逻辑上将其中一些类组合成一个简化的接口,可以让这些类更易于使用.为子类系统中一组不同接口提供统一接口的一种方式称为外观模式.
结构图
- 外观模式为子系统中一组不同的接口提供统一的接口.外观定义了上层接口,通过降低复杂度和隐藏子系统间的通信及依赖关系,让子系统更易于使用.
- 外观模式起到整个子系统入口的作用.有些客户端只需要子系统的某些基本行为,而对子系统的类不做太多定制,外观为这样的客户端提供简化的接口.
- 只有需要从某些子系统的类定制更多行为的客户端,才会关注外观背后的细节.
例子:乘客打车去目的地,只要发送一个指令"我要去xx地",就可以简化司机开车,开什么车,怎样开车,走什么路线这样一个复杂的过程. 下面是这个列子的使用外观模式实现的类图
使用场景
在以下情形,会考虑使用外观模式:
- 子系统正逐渐变的复杂. 应用模式的过程中演化出许多类.可以使用外观为这些子系统类提供一个较简单的接口.
- 可以使用外观对子系统进行分层. 每个子系统级别有一个外观作为入口点. 让它们通过其外观进行通信,可以简化它们的依赖关系.
外观模式:为系统中的一组接口提供一个统一的接口. 外观定义了一个高层接口,让子系统更易于使用.
对象去耦-Decoupling of Objects
9.中介者-Mediator ※※※
定义
- 面向对象的设计鼓励把行为分散到不同对象中.这种分散可能导致对象之间的相互关联.在最糟糕的情况下,所有对象都彼此了解相互操作.
- 虽然把行为分散到不同对象增强了可复用性,但是增加的相互关联又减少了获得的益处.增加的关联使得对象很难或不能依赖其他对象的情况下工作.应用程序的整体行为可能难以进行任何重大修改,因为行为分布于许多对象.于是结果可能是创建越来越多的子类,以支持应用程序的任何新行为
- 中介者模式用于定义一个集中的场所,对象间的交互可以在一个中介者对象处理.其他对象不必彼此交互,因此减少了它们之间的依存关系.
中介者模式:用一个对象来封装一系列对象的交互方式. 中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
类图
- 抽象的Mediator定义了用于同Colleague交互的一般行为
- 具体的同事colleague是以明确定义的方式进行相互交互通信的对象,并且彼此紧密依存. ConcreteMediator为Concrete Colleague定义了更加具体的行为,因此可以子类化Mediator,把各种Colleague交互算法应用到相同或不同的Colleague类型.如果应用程序只需要一个中介者,有时抽象的Mediator可以省略.
- Colleague的实例有一个Mediator实例的引用,同时Mediator的实例知道参与这个组织的每个对象.
运行时中介者模式的一种可能的对象结构图:
使用场景
在以下情形,会考虑使用中介者模式:
- 对象间的交互虽定义明确然而非常复杂,导致一组对象彼此相互依赖而且难以理解
- 因为对象引用了许多其他对象并与其通讯,导致对象难以复用;
- 想要定制一个分布在许多个类中的逻辑或行为,又不想生成太多子类
10.观察者-Observer
定义
- 观察者模式也叫发布-订阅模式. 如它的别名所指那样,它很像杂志订阅.当从杂志发行商订阅杂志的时候,读者把名字和邮寄地址提供给发行商,这样新的一期就能送到读者手上.
- 观察者模式可以消除不同对象间的耦合(或者用其他不同的行为来扩展现有的行为).通过这一模式,不同对象可以协同工作,同时它们也可以被复用于其他地方.
观察者模式: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.
类图
- 观察者模式是一种发布-订阅模型. Observer从Subject订阅通知. ConreteObserver实现抽象Observer并重载Update方法.
- 一旦Subject的实例需要通知Observer任何新的变更,Subject会发送update消息来通知存储在内部表中的所有注册的Observer
- 在ConcreteObserver的update方法的实际实现中,Subject的内部状态可被取得并在以后进行处理.
- 使用观察者模式的一个最明显的好处是,可以用N个Observer来扩展Subject的行为,这些Observer具有处理存储在Subject中信息的特定实现.
- 它也是一种用于消除不同对象间耦合的一种设计模式
使用场景
在以下情形,会考虑使用观察者模式:
- 有两种抽象类型相互依赖,将他们封装在各自的对象中,就可以对它们单独进行改变和复用.
- 对一个对象的改变需要同时改变其他对象,而不知到具体有多少对象有待改变.
- 一个对象必须通知其他对象,而它又不需要知道其他对象是什么
抽象集合-Abstract Collection
11.组合-Composite
定义
- 组合模式让我们可以把相同基类型(base type)的对象组合到树状结构中,其中的父节点包含同类型的子节点. 换句话说,这种树状结构形成"部分-整体"的层次结构.
- 是一种既包含对象的组合(容器)又包含作为"叶节点"的单个对象的一种层次结构.
- 每个组合体包含的其他节点,可以是叶节点或者是其他组合体.这种关系在这个层次结构中递归重复.
- 每个组合或叶节点有相同的基类型,同样的操作可应用于他们中的每一个,而不必在客户端作类型检查.
- 客户端对组合与叶节点进行操作是可以忽略它们之间的差距.
- 基接口(base interface)是定义了Leaf类和Composite类的共同操作的Component
- 有些操作只对Composite类有意义,这样是为了不暴露内部细节给客户端
组合模式: 将对象组合成树形结构以表示"部分-整体"的层次结构. 组合使得用户对单个对象和组合对象的使用具有一致性
使用场景
在以下情形,会考虑使用组合模式:
- 想获得对象抽象的树形标识(部分-整体层次结构)
- 想让客户端统一处理组合结构中所有对象.
12.迭代器-Iterator
定义
- 在面向对象软件中,针对抽象集合迭代行为的设计模式叫做迭代器(Iterator).
- 迭代器提供了一种顺序访问聚合对象(集合)中元素的方法,而无需暴露结构底层表示和细节
- 遍历集合中元素的智能从集合本身转移到迭代器对象.
- 迭代器定义了一个用于访问集合元素并记录当前元素的接口.
- 不同的迭代器可以执行不同的遍历策略
类图
- List定义了修改集合以及放回集合中元素个数的方法
- ListIterator保持一个对List对象的引用,以便迭代器遍历结构中的元素并将其返回
- ListIterator定义了让客户端从迭代过程中访问下一项的方法.
- 迭代器有个内部的index_变量,记录集合中的当前的位置
- 抽象的Aggregate定义了createIterator方法,它返回一个Iterator对象.
- ConcreteAggregate对象子类化Aggregate,重载其createIterator方法并返回ConcreteIterator的实例.
- Iterator抽象类定义了所有Iterator应具有的基本行为,
- 客户端会使用定义好的抽象接口来遍历任何Aggretate类型对象中的元素
迭代器: 提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示
外部迭代器与内部迭代器
- 外部迭代器让客户端直接操作迭代过程,所以客户端需要知道外部迭代器才能使用
- 另外一种情况是,集合对象(被迭代的目标对象)在其内部维护并操作一个内部迭代器
- 提供内部迭代器的典型的集合对象为客户端定义了一个接口,或者从底层的集合一次访问一个元素,或者向每个元素发送消息,外部迭代器与内部迭代器的区别总结在下表:
外部迭代器 | 内部迭代器 |
---|---|
客户端需要知道外部迭代器才能使用,但是它为客户端提供了更多的控制 | 客户端不需要知道任何外部迭代器,而是可以通过集合对象的特殊接口,或者一次访问一个元素,或者向集合中的每个元素发送消息 |
客户端创建并维护外部迭代器 | 集合对象本身创建并维护它的内部迭代器 |
客户端可以使用不同的外部迭代器实现多种类型的遍历 | 集合对象可以在不修改客户端代码的情况下,选择不同的内部迭代器 |
使用场景
在以下情形,会考虑使用迭代器模式:
- 需要访问组合对象的内容,而又不暴露其内部表示
- 需要通过多种方式遍历组合对象
- 需要提供一个统一的接口,用来遍历各种类型的组合对象
行为扩展-Behavioral Extension
13.访问者-Visitor
定义
在软件设计中,如果架构师为了扩展类的功能而往一个类里塞进了太多方法,类就会变得极为复杂.更好的做法是创建外部的类来扩展它,而对原始代码不做太多改动.
- 访问者模式涉及两个关键角色(或者说组件): 访问者和它访问的元素. 元素可以是任何对象,但通常是"部分-整体"结构中的节点.
- 部分-整体结构包含组合体与叶节点,或者任何其他复杂的对象结构,元素本身不仅限于这些种类的结构
- 访问者知道复杂结构中每个元素,可以访问每个元素的节点,并根据元素的特征,属性或操作执行任何操作.
类图
- Visitor协议声明了两个很像的visit*方法,用于访问和处理各种Element类型的对象.ConcreteVisitor(1或2)实现这一协议及其抽象方法.
- Visit*的操作定义了针对特定Element类型的适当操作. Client创建ConcreteVisit(1或2)的对象,并将其传给一个Element对象结构.
- Element对象结构中有一个方法接受一般化的Visistor类型.
访问者模式的这个承包商版本,反映了从房屋承包商的例子角度的一种实现.
- Plumber(管道工)和Electrician(电工)是访问者.
- House是个复杂的结构,包含有Fixable抽象物品对象,Constructor(承包商)可对其进行访问并修理.
- Plumber可以用其专有个visitPlumbing:操作,访问House的Plumbing(管路)结构.
- 电工可用他的visitElectrical:操作,以同样的理由对同一个House的Electrical(电路)组件进行访问.
- 普通的Contractor好像既会修Plumbing又会修Electrical,但实际上,它把这些工作转包给能实际完成工作的人.
访问者模式:表示一个作用于对某象结构中的各元素的操作,它让我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作
使用场景
在以下情形,会考虑使用访问者模式:
- 一个复杂的对象结构包含很多其他对象,他们有不同的接口(比如组合体),但是对这些对象实施一些依赖于其具体类型的操作.
- 需要对一个组合结构中的对象进行很多不相关的操作,但是不想让这些操作"污染"这些对象的类,可以将相关的操作集中起来,定义在一个访问者类中
- 定义复杂结构的类很少修改,但经常需要向其添加新的操作.
14.装饰-Decorator
定义
在面向对象软件设计中,向对象添加"东西"(行为),而不破坏其原有风格,因此增强了的对象是同一类的加强版(如带相框的照片).任何"增强"(相框)均可以动态添加和删除,我们把这种设计模式叫做"装饰",装饰对象可以附加到另一个装饰对象,也可以附加到原始对象,对其功能进行扩展,同时保留原始行为不受影响.
类图
- 标准的装饰模式包括一个抽象Component父类,它为其他具体组件(component)声明了些操作.
- 抽象的Component类可被细化为另一个叫做Decorator的抽象类.
- Decorator包含了另一个Component的引用.ConcreteDecorator为其他Component或Decorator定义了几个扩展行为,并且在自己的操作中执行内嵌的Component操作.
- 默认的operation方法只是向内嵌的component发送一个消息.
- ConcreteDecoratorA和ConcreteDecoratorB重载父类的operation,通过super把自己增加的行为扩展给component的operation.
- 如果只需要向component添加一种职责,那么就可以省掉抽象的Decorator类,让ConcreteDecorator直接把请求转发给component.
- 如果有以此方式连接的对象,那么就好像形成了一种操作链,把一种行为添加到另一个种之上
使用场景
在以下情形,会考虑使用装饰模式:
- 想要在不影响其他对象的情况下,以动态,透明的方式给单个对象添加职责
- 想要扩展一个类的行为,却做不到.类定义可能被隐藏,无法进行子类化; 或者,对类的每个行为的扩展,为支持每种功能组合,将产生大量的子类
- 对类的职责的扩展是可选的.
装饰模式: 动态地给一个对象添加一些额外的职责. 就扩展功能来说,装饰模式相比生成子类更为灵活.
改变对象的"外表"和"内容"
15.责任链-Chain of Responsibility
定义
- 责任链模式的主要思想是,对象引用了同一类型的另一个对象,形成一条链.链中的每个对象实现了同样的方法,处理对链中第一个对象发起的同一个请求.
- 如果一个对象不知道如何处理请求,它就把请求传给下一个响应器(即successor)
- Handler是上层抽象类,定义了一个方法----HandleRequest,处理它知道如何处理的请求对象.
- ConcreteHandler1和ConcreteHandler2实现了handleRequest方法,来处理他们认识的请求对象. Handler也有一个指向另一个同类型实例的引用,即successor. 当调用Handler实例的handleRequest消息时,如果这个实例不知道如何处理请求,它会用同样的消息把请求转给successor.
- 如果successor可以处理,就行了,否则,它就把请求传给下一个successor(如果有的话)
- 这个过程会一直进行下去,直到请求被传到链中的最后一个Handler.
- aClient有一个对Handler实例的引用,叫aHandler. aHandler是处理程序链的第一个对象,即aConcreteHandler,
- aConcreteHandler用它内部的successor引用跟另一个Handler实例连接起来.
- 用这种策略处理请求的最低要求是,如果不懂如何处理请求,就传给下个处理程序
使用场景
在以下情形,会考虑使用责任链模式:
- 有多个对象可以处理请求,而处理程序只有在运行时才能确定
- 向一组对象发出请求,而不想显式指定处理请求的特定程序
责任链模式: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间发生耦合. 此模式将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止
算法封装-Algorithm Encapsulation
16.模板方法-Template Method
定义
模板方法模式是面向对象软件设计中一种非常简单的设计模式.其基本思想是在抽象类的一个方法中定义"标准"算法.在这个方法中调用的基本操作应由子类重载予以实现.这个方法被称为"模板",因为方法定义了的算法缺少一些特有的操作.
模板方法模式: 定义一个操作中算法的骨架,而将一些步骤延迟到子类中.模板方法使子类可以重定义算法的某些特定步骤而不改变该算法的结构.
类图
因为方法定义的算法缺少一些特有的操作.上图显示了抽象类与具体子类之间的关系----抽象类定义模板,子类重载基本操作以提供独特操作模板方法调用.
- AbstractClass不完整地定义了一些方法与算法,留出一些操作未定义.
- AbstractClass调用的templateMethod时,方法中为定义的空白部分,由ConcreteClass重载primitiveOperation1/2来填补
使用场景
在以下情形,会考虑使用模板方法:
- 需要一次性实现算法的不变部分,并将可变的行为留给子类来实现.
- 子类的共同行为应该提取出来方法放到公共类中,以避免代码重复.现有代码的差别应该被分离为新的操作,然后用一个调用这些新操作的模板方法来替换这些代码
- 需要控制子类的扩展.可以定义一个在特定点调用"钩子(hook)"操作的模板方法.子类可以通过对钩子操作的实现在这些扩展功能.
钩子操作给出了默认行为,子类可对其扩展. 默认行为通常什么都不做.子类可以重载这个方法,为模板算法提供附加的操作.
模板方法模式中的控制结构流程是倒转的,因为父类的模板方法调用其子类的操作,而不是子类调用父类的操作.
模板方法会调用5种类型的操作:
- 对具体类或客户端类的具体操作
- 对对象类的具体操作.
- 抽象操作
- 工厂方法
- 钩子操作
17.策略-Strategy
定义
- 面向对象软件设计中,我们可以把相关算法分离为不同的类,成为策略.与这种做法有关的一种设计模式称为策略模式.
- 策略模式中的一个关键角色就是策略类,它为所有支持的或相关的算法声明了一个共同接口.另外还有使用策略接口来实现相关算法的具体策略类.
- 场景(context)类对象配置有一个具体策略对象实例,场景对象使用策略接口调用由具体策略类定义的算法
策略模式: 定义一系列算法,把它们一个个封装起来,并且使它们可相互替换.该模式使得算法可以独立与使用它的客户端而变化.
类图
- 一组算法,或者说算法的一个层次结构,以ConcreteStrategy(A,B,C)类的形式,共享相同的algorithmInterface接口,这样Context就能使用相同的接口访问算法的各种变体.
- Context的实例可以在运行时用不同的ConcreteStrategy对象进行配置.这样可以理解成更换Context对象的"内容",因为变更是发生在对象的内部.
- 装饰器改变对象的"外表",因为修改是从外面叠加起来.两者的区别的详细对比可以参考上面的装饰器模式中的讨论
模型-视图-控制器中的策略模式 |
---|
模型-视图-控制器模式中,控制器决定视图对模型数据进行显示的时机和内容. 视图本身知道如何绘图,但需要控制器告诉它要显示的内容. 同一个视图如果与不同的控制合作,数据的输出格式可能一样,但数据 的类型和格式可能随不同控制器的不同输出而不同.因此这种情况下的控制器是视图的策略.在前面的几节提到过,控制器与视图之间是一种基于策略模式的关系. |
使用场景
在以下情形,会考虑使用策略模式:
- 一个类在其操作中使用多个条件语句来定义许多行为.我们可以相关的条件分支移到他们自己的策略类中
- 需要算法的各种变体
- 需要避免把复杂的,与算法相关的数据结构暴露给客户端
18.命令-Command
定义
- 在面向对象设计中,我们把指令封装在各种命令对象中.命令对象可以被传递并且在制定时刻被不同的客户端复用.从这一概念精心设计而来的设计模式叫做命令模式
- 命令对象封装了如何对目标执行指令的信息,因此客户端或调用者不必了解目标的任何细节,却仍可以对它执行任何已有的操作.
- 通过把请求封装成对象,客户端可以把参数化并置入队列或日志中,也能够支持可撤销的操作.
- 命令对象将一个或多个动作绑定到特定的接收器.命令模式消除了作为对象的动作和执行它的接收器之间的绑定.
命令模式: 将请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排对或记录请求日志,以及支持可撤销的操作
类图
- Client(客户端) 创建ConcreteCommand对象并设定其receiver(接收器)
- Invoker要求通用命令(实际上是ConcreteCommand)实施请求
- Command是为Invoker所知的通用接口(协议)
- ConcreteCommand起Receiver和对它的操作action之间的中间人的作用
- Receiver可以是随着由Command(ConcreteCommand)对象实施的相应请求,而执行实际操作的任何对象.
使用场景
在以下情形,会考虑使用命令模式:
- 想让应用程序支持撤销和恢复
- 想用对象参数化一个动作以执行操作,并用不同命令对象来代替回调函数
- 想要在不同时刻对请求进行指定,排列和执行.
- 想要记录修改日志,这样在系统故障时,这些修改可在后来重做一遍.
- 想让系统支持事务(transaction),事务封装了对数据的一系列修改.事务可以建模为命令对象
性能与对象访问-Performance and Object Access
19.享元-Flyweight
定义
在面向对象软件设计中,利用公共对象不仅能节省资源还能提高性能. 比方说,某个任务需要一个类的一百万个实例,但我们可以把这个类的一个实例让大家共享,而把某些独特的信息放在外部,节省的资源可能相当可观(一个实例与一百万个实例的差别).共享的对象只提供某些内在的信息,而不能用来识别对象.专门用于设计可共享对象的一种设计模式叫做享元模式(Flyweight pattern)
- 实现享元模式需要两个关键组件,通常是可共享的享元对象和保存它们的池.某种中央对象维护这个池,并从它返回适当的实例
- 通过享元模式能够节省的空间总量,某些对象的独特状态(外在状态)可以拿到外部,在别处管理,其余部分被共享.
享元模式: 运用共享技术有效地支持大量细粒度的对象
类图
- Flyweight是两个具体享元类ConcreteFlyweight和ConcreteFlyweight2的父接口(协议)
- 每个ConcreteFlyweight类维护不能用于识别对象的内在状态intrinsicState.
- Flyweight声明了operation:extrinsicState方法,由这两个ConcreteFlyweight类实现.
- intrinsicState是享元对象中可被共享的部分,而extrinsicState补充缺少的信息,让享元中的独一无二的信息完成任务
使用场景
在以下情形,会考虑使用享元模式:
- 应用程序使用很多对象
- 在内存中保存对象会影响内存性能
- 对象的多数特有状态(外在状态)可以放到外部而轻量化
- 移除了外在状态之后,可以用较少的共享对象来代替原来的那组对象
- 应用程序不依赖于对象标识,因为共享对象不能提供唯一的标识
20.代理-Proxy
定义
有以下几种代理:
- 远程代理(remote proxy)为位于不同地址空间或网络上的对象提供本地代表
- 虚拟代理(virtual proxy)根据需要创建重型对象
- 保护代理(protection proxy)根据各种访问权限控制对原对象的访问
- 智能引用代理(smart-reference proxy)通过对真正对象的引用进行计数来管理内存. 也用于锁定真正对象,让其他对象不能对其进行修改
代理模式:为其他对象提供一种代理以控制对这个对象的访问,通常,代理是一种替代或者占位,它控制对另一些对象的访问,而这些对象可能是远程对象,创建开销较大的对象,或者是对安全性要求高的对象
类图
- 当client向Proxy对象发送request消息是,Proxy对象会把这个消息转发给Proxy对象之中的RequestSubject对象.RealSubjec会实施实际的操作间接的满足客户端的需求.
- 在运行时,我们可以想象这样一个场景:客户端以抽象类型引用一个对象.这个引用实际上是个Proxy对象.Proxy对象本身有一个对RealSubject实例的引用,以后如果接到请求,此实例将执行高强度的工作.场景如下图:
使用场景
在以下情形,会考虑使用代理模式:
- 需要一个远程代理,为位于不同地址空间或者网络中的对象提供本地代表
- 需要一个虚拟代理,来根据要求创建重型对象.
- 需要一个保护代理,来根据不同访问权限控制对原对象的访问
- 需要一个只能引用代理,通过对实体对象的引用进行计数来管理内存. 也能用于锁定实体对象,让其他对象不能修改它.
对象状态-State of Object
21.备忘录-Memento
定义
在响应某些事件时, 应用程序需要保存自身的状态,比如当用户保存文档或程序退出时.例如,游戏退出之前,可能需要保存当前会话的状态,如游戏等级,敌人数量,可用武器的种类等.游戏再次打开时,玩家可用从离开的地方接着玩.很多时候,保存程序的状态真的不需要什么特别奇妙的方法.任何简单有效的方法都可以,但是同时,保存信息应该只对原始程序有意义.原始程序应该是能够解码它所保存文档中的信息的唯一实体.这就是备忘录模式应用于游戏/文字处理等程序的软件设计中的方式,这些程序需要保存当前上下文的复杂状态的快照并在以后恢复.
备忘录模式: 在不破坏封装的前提下,捕获一个对象内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态
类图
这个模式中有三个关键角色:原发器(originator)/备忘录(memento)和看管人(caretaker).其思想非常简单,原发器创建一个包含其状态的备忘录,并传给看管人.看管人不知如何与备忘录交互,但会把备忘录放在安全之处保管好
- 当看管人请求Originator对象保存其状态时,Originator对象将使用其内部状态创建一个新的Memento实例.然后看管人保管Memento对象,或者把它保存到文件系统,一段时间后再把它传回给Originator对象
- Originator对象不知道这个Memento对象将如何被保管.看管人也不知道Memento对象里是啥
- 这样设计的关键是维护Memento对象的私有性,只让Originator对象访问保存在Memento对象中的内部状态(即Originator过去的内部状态).
- Memento类应该有两个接口:一个宽接口,给Originator用,一个窄接口,给其他对象用,所以前面的图中,
setState:
,state
,init
这些方法应该定义为私有的,不让Originator和Memento以外的对象使用
使用场景
在以下情形,会考虑使用备忘录模式:
- 需要保存一个对象(或某部分)在某一时刻的状态,这样以后就可以回到先前的状态
- 用于获取状态的接口会暴露实现的细节,需要将其隐藏起来.