0%

目的

动态地向对象添加额外功能,比创建子类更加灵活

示例

GUI 工具箱允许你对任意一个用户界面组件添加一些特性(例如边框),或是一些行为(例如窗口滚动)。

一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为装饰

  • 这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明(因此可以递归地嵌套)
  • 它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。 600

适用性

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤销的职责。
  • 当不能采用生成子类的方法进行扩充时,例如:
    • 可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长
    • 类定义被隐藏或类是 final 的

结构

  • Component:对象接口
  • ConcreteComponent
  • Decorator:维护指向 Component 的指针,定义和 Component 一致的接口
  • ConcreteDecorator:向组件添加职责(将操作代理给所持有的对象,并附加自身的操作)

优缺点

  • 比静态继承更灵活:
    • 可以用添加和分离装饰器的方法,用装饰在运行时增加和删除职责
    • 可以很容易地重复添加一个特性
  • 避免在层次结构高层的类有太多的特征
  • Decorator 相比 Component 模式而言,是完全透明的包装,使用装饰时不应当依赖对象标识

缺点:有许多小对象:采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在它们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难

实现

  • 接口一致性:装饰器必须和被装饰的对象接口一致
  • 省略抽象的装饰器:仅需要添加一个职责时,没有必要定义抽象 Decorator 类
  • 保持 Component 类的简单性:为保证一致性,组件和装饰器必须有共同的 Component 父类,因此需要避免这个类过大。这个父类应集中于定义接口而不是存储数据,对数据表示的定义应延迟到子类中

相关模式

  • Adapter(4.1):Decorator 模式不同于 Adapter 模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。
  • Composite(4.3):可以将装饰视为一个退化的、仅有一个组件的组合。然而,装饰仅给对象添加一些额外的职责——它的目的不在于对象聚集。

装饰器 VS 策略模式

改变对象的行为主要有两种方式:

  • Decorator 模式改变外壳
  • Strategy 模式改变内核

当 Component 类原本就很庞大时,使用 Decorator 模式代价太高, Strategy 模式相对好一些

用途

  • 定义一个高层接口,为子系统的一组接口提供一个一致的界面,使得子系统更加易用
  • Facade/fəˈsɑd/ 还可以降低子系统间的依赖关系

示例

有一个编译子系统,包含了若干个类来实现这一编译器,如 Scanner、Parse 等。

  • 多数普通用户不关心语法分析等细节,只是希望实现一些代码。
    • 因此子系统提供一个 Compiler 对象提供统一的高层接口,隐藏了内部实现
  • 对于了解内部细节、有特殊需求的高级用户,可以绕过外观类 Complier 使用低层类实现功能
    • 外观类不会隐藏内部实现

适用性

  • 当你要为一个复杂子系统提供一个简单接口时。
    • Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够
    • 而那些需要更多的可定制性的用户可以越过 Facade 层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 Facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用 Facade 模式定义子系统中每层的入口点。
  • 如果子系统之间是相互依赖的,可以让它们仅通过 Facade 进行通信,从而简化了它们之间的依赖关系。

结构

  • Facade:知道系统的内部结构,并将客户请求代理给适当的子系统对象
  • Subsystemclasses:
    • 实现子系统的功能,处理 Facade 分派的任务
    • 没有 Facade 的任何信息

使用 Facade 的客户程序不需要直接访问子系统对象,而通过发送请求给 Facade 的方式与子系统通信。Facade 将这些消息转发给适当的子系统对象。

优缺点

优点:

  • 对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
  • 它实现了子系统与客户之间的松耦合关系(子系统内部的功能组件往往是紧耦合的)
    • Facade 模式有助于建立层次结构系统
    • Facade 模式可以消除复杂的循环依赖关系
    • Facade 模式同样也有利于降低编译依赖性,使得编译一个子系统一般不需要编译所有其他的子系统。
  • 如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。

实现

  • 进一步降低客户-子系统耦合度的方法:
    • Facade 定义为抽象类而其具体子类对应不同的子系统实现可以进一步降低客户和子系统的耦合度
    • 还可以用不同的子系统对象配置 Facade 对象
  • 公共子系统类与私有子系统类:子系统和一个类都可用于功能的封装,相应地可以考虑提供公有和私有接口
    • 子系统的公共接口包含所有的客户程序可以访问的类(包括 Facade),私有接口仅用于对子系统进行扩充

相关模式

  • Abstract Factory(3.1)模式可以与 Facade 模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。 Abstract Factory 也可以代替 Facade 模式隐藏那些与平台相关的类。

目的

为其他对象提供一种代理以控制该对象的访问。 类似网络中的反向代理

示例

需求:

  • 为了改善打开执行速度,文档编辑器应当按需创建开销很大的对象(例如大型图像)
  • 需要隐藏根据需要创建图像这一事实,从而不会使得编辑器的实现复杂化

方法:

  • 使用图像 Proxy 替代真正的图像,Proxy 可以代替一个图像对象,并且在需要(文档编辑器调用图像代理的 Draw 操作)时负责实例化这个图像对象

  • 图像 Proxy 存储图像路径和分辨率,以及对真正的 Image 实例的指引。因此不需要真正实例化这个图像就可以响应格式化程序对图像尺寸的请求

适用性

在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用 Proxy 模式。例如:

  • 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代表
  • 虚代理(Virtual Proxy)根据需要创建开销很大的对象。
  • 保护代理 (Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  • 智能指针(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
    • 对指向实际对象的引用计数,可用于内存自动回收
    • 当第一次引用一个持久对象时,将它装入内存
    • 读写锁:在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它
  • copy-on-write:用代理延迟复杂大对象的拷贝过程,保证只有当这个对象被修改的时候才对它进行拷贝
    • 在实现 Copy-on-write 时必须对实体进行引用计数。
      • 拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时,代理才会真正地拷贝它。
      • 当引用的数目为零时,这个实体将被删除。

结构

  • Proxy:
    • 基本功能
      • 保存一个引用使得代理可以访问实体。若 RealSubject 和 Subject 的接口相同,Proxy 会引用 Subject。
      • 提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。
      • 控制对实体的存取,并可能负责创建和删除它。
    • 其他功能:
      • Remote Proxy 负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
      • Virtual Proxy 可以缓存实体的附加信息,以便延迟对它的访问。例如,动机一节中提到的 ImageProxy 缓存了图像实体的尺寸。
      • Protection Proxy 检查调用者是否具有实现一个请求所必需的访问权限。
  • Subject(Graphic):定义 RealSubject 和 Proxy 的共用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。
  • RealSubject(Image):定义 Proxy 所代表的实体。

代理根据其种类,在适当的时候向 RealSubject 转发请求

行为型模式涉及算法和对象间职责的分配。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式

  • 类行为型模式使用继承机制在类间分派行为
  • 对象行为型模式描述了一组对等的对象怎样相互协作

大多数模式有两种对象:封装该方面特征的新对象,使用这些新对象的已有对象。

  • 不使用这些模式的话,通常这些新对象的功能就会变成已有对象的难以分割的一部分。

通信的封装/协作

  • Observer 通过引入 Observer 和 Subject 对象来分布通信
  • Mediator 对象则封装了其他对象间的通信

解耦合

当合作的对象直接互相引用时,它们变得互相依赖。命令、观察者、中介者和职责链等模式都涉及如何对发送者和接收者解耦,但它们又各有不同的权衡考虑。

命令模式使用一个 Command 对象来定义发送者和接收者之间的绑定关系

  • Command 对象提供了一个提交请求的简单接口(即 Execute 操作)。
  • 将发送者和接收者之间的连接定义在一个单独的对象中使得该发送者可以与不同的接收者一起工作。

观察者模式通过定义一个接口来通知目标中发生的改变,从而将发送者(目标)与接收者(观察者)解耦

  • 当对象间有数据依赖时,最好用观察者模式来对它们进行解耦。

中介者模式让对象通过一个 Mediator 对象间接地互相引用,从而对它们解耦

  • 它将通信行为集中到一个类中而不是将其分布在各个子类中
  • 为增加灵活性 Mediator 可能不得不实现它自己的分发策略(例如用一定方式对请求编码并打包参数),但这通常会导致类型安全问题

职责链模式通过沿一个潜在接收者链传递请求而将发送者与接收者解耦

目的

定义一个操作的算法骨架,而将一些步骤延迟到子类中。通过子类来实现这些步骤,使得不改变算法结构的情况下,可重新定义算法中的某些特定步骤

适用性

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
    • 首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。
    • 然后,用一个调用新的操作的模板方法来替换不同的代码。
  • 控制子类扩展。模板方法只在特定点调用 hook 操作,这样就只允许在这些点进行扩展。

结构

  • AbstractClass:
    • 定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法的各步骤
    • 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在 AbstractClass 或其他对象中的操作。
  • ConcreteClass:实现原语操作以完成算法中与特定子类相关的步骤。

效果

模板方法是一种代码复用的基本技术。它们在类库中尤为重要,提取了类库中的公共行为。 模板方法导致一种反向的控制结构,父类调用一个子类的操作。

模板方法调用下列类型的操作:

  • 原语操作(即抽象操作):子类必须重载以提供具体实现。
  • 钩子操作(hook operation),它提供了缺省的行为,子类可以在必要时通过重载进行扩展。钩子操作在缺省情况下通常是空操作。
  • 其他操作:不应重定义这些操作

很重要的一点是模板方法应该区分哪些操作是原语操作(必须重载),哪些是钩子操作(可以重载)

实现

  • 访问控制:
    • 在 C++中,一个模板方法调用的原语操作可以被定义为 protected 的纯虚函数,保证它们只被模板方法调用,同时必须重载。
    • 模板方法自身不需要被重定义,因此可以将模板方法定义为一个非虚成员函数。
  • 尽量减少原语操作
  • 命名约定:可以给应被重定义的操作的名字加上一个前缀以识别它们

相关模式

Strategy(5.9):模板方法使用继承来改变算法的一部分, Strategy 使用委托来改变整个算法。 Factory Method(3.3)常被模板方法调用。

目的

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

示例

考虑一个编译器,它将源程序表示为一个抽象语法树,对于语法树结点需要进行不同的处理:类型检查,代码优化等

  • 将操作和结点类耦合带来的问题:将所有这些操作分散到各种结点类中会导致整个系统难以理解、难以维护和修改;此外,增加新的操作通常需要重新编译所有这些类
  • 解决方法:将每一个类中相关的操作包装在一个独立的对象(称为一个 Visitor)中,并在遍历抽象语法树时将此对象传递给当前访问的元素
    • 所有抽象语法树的访问者有一个抽象的父类 NodeVisitor。NodeVisitor 必须为每一个结点类定义一个操作 使用 Visitor 模式,必须定义两个类层次:
  • 一个对应于接受操作的元素(Node 层次)
  • 另一个对应于定义对元素的操作的访问者(NodeVisitor 层次)。给访问者类层次增加一个新的子类即可创建一个新的操作。

适用性

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。

结构

  • Visitor:接口。为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作,该操作的名字和特征标识了发送 Visit 请求给该访问者的类。

    • 访问者就可以通过该元素的特定接口直接访问它。
  • ConcreteVisitor:实现每个由 Visitor 声明的操作。每种具体的 Visitor 实现一种特定操作

  • Element,ConcreteElement:定义一个 Accept 操作,它以一个访问者为参数

  • ObjectStructure(例如 List,Map):能枚举它的元素,可以提供一个高层的接口以允许该 Visitor 访问它的元素

  • 一个使用 Visitor 模式的客户必须创建一个 ConcreteVisitor 对象,然后遍历该对象结构,并用该访问者访问每一个元素。

  • 当一个元素被访问时,它调用对应于它的类的 Visitor 操作。

效果

  • 访问者模式使得易于增加新的操作:仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反,如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一个类。

  • 访问者集中相关的操作而分离无关的操作:相关的行为不是分布在定义该对象结构的各个类上,而是集中在一个访问者中;无关行为却被分别放在各自的访问者子类中

  • 增加新的 ConcreteElement 类很困难:每添加一个新的 ConcreteElement 都要在 Vistor 中添加一个新的抽象操作,并在每一个 ConcretVisitor 类中实现相应的操作。

目的

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

示例

一个聚合对象,如列表(list),需要:

  • 提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构
  • 可能要以不同的方式遍历
  • 不希望列表的接口中充斥着各种不同遍历的操作

KEY:将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器(iterator)对象中,由迭代器对象跟踪当前元素,维护遍历序列

  • 将遍历机制与列表对象分离使我们可以对同一个容器定义不同的迭代器来实现不同的遍历策略。例如先序迭代和后序迭代

多态迭代

  • 为何需要多态:上述方法中,迭代器和列表是耦合在一起的,而且客户对象必须知道遍历的是一个列表而不是其他聚合结构
  • 方法:使用继承和多态机制,并由容器提供 CreateIterator 的接口,创建自己兼容的某种迭代器
    • 这是 Factory Method,产生两个类层次,一个是列表的,一个是迭代器的。 CreateIterator“联系”这两个类层次

适用性

  • 访问一个聚合对象的内容而无须暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)。

结构

  • Iterator:定义访问和遍历元素的接口
  • ConcreteIterator:
    • 实现迭代器接口
    • 对该聚合遍历时跟踪当前位置,并能够计算出待遍历的后继对象
  • Aggregate(聚合):定义创建相应迭代器对象的接口。
  • ConcreteAggregate:实现创建相应迭代器的接口,该操作返回 ConcreteIterator 的一个适当的实例

优缺点

迭代器模式有三个重要的作用:

  • 支持以不同的方式遍历一个聚合
  • 简化了聚合的接口:有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了
  • 在同一个聚合上可以同时有多个遍历:每个迭代器保持它自己的遍历状态,因此你可以同时进行多个遍历

实现

谁控制迭代

控制迭代即控制迭代的推进过程,根据迭代过程是迭代器还是使用该迭代器的客户控制,迭代器可分为内部/外部迭代器 2 类:

  • 外部迭代器:客户必须主动推进遍历的步伐,显式地向迭代器请求下一个元素
  • 内部迭代器:由迭代器控制迭代的推进,客户只需向其提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作。

谁定义遍历算法

迭代器和聚合均可定义遍历算法

聚合定义遍历算法聚合定义遍历算法,迭代器只存储当前迭代的上下文

  • 在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为游标(cursor),因为它仅用来指示当前位置。
  • 客户会以这个游标为参数调用该聚合的 Next 操作,而 Next 操作将改变这个指示器的状态

迭代器定义遍历算法

  • 优点:
    • 易于在相同的聚合上使用不同的迭代算法
    • 易于在不同的聚合上复用相同的算法
  • 缺点:遍历算法可能需要访问聚合的私有变量。如果这样,将遍历算法放入迭代器中会破坏聚合的封装性

遍历时修改

在遍历一个聚合的同时更改这个聚合可能是危险的。如果在遍历聚合的时候增加或删除聚合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。

一个健壮的迭代器 robust iterator 保证==插入和删除操作不会干扰遍历,且不需要拷贝该聚合==

  • 这大多数需要向聚合注册迭代器。当插入或删除元素时,该聚合需要:
    • 调整迭代器的内部状态
    • 或在内部维护额外的信息以保证正确的遍历。

可选接口

迭代器的最小接口由 First、Next、IsDone 和 CurrentItem 操作组成

目的

用一个中介对象封装对象交互,使各对象不需要显式地相互引用

  • 从而可以解耦合
  • 可以对立改变之间的交互

示例

面向对象中,对象的行为分布到各个对象中。这种分布可能会导致对象间有许多连接。在最坏的情况下,每一个对象都知道其他所有对象。

考虑 GUI 中各个窗口组件的依赖关系:例如当一个特定的输入域为空时,某个按钮不能使用;ListBox 选择一项可能会改变一个输入域的内容

  • 这导致每个组件必须单独定制以反应组件间的依赖关系
  • 解决方法:可以通过将集体行为封装在一个单独的中介者(mediator)对象中,如下图所设

类层次结构: 对象交互: 例如选择 ListBox 改变 EntryField 的内容

适用性

  • 一组对象以定义良好但复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
  • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。

结构

  • Mediator 中介者:定义和同事 Colleague 通信的接口
  • ConcreteMediator:了解并维护它的各个同事,处理它们之间的交互
  • Colleagueclass:
    • 每个同事均了解其中介对象(例如持有一份 Mediator 实例的引用)
    • 每个同事通过中介的接口与其他同事通信

同事向一个中介者对象发送和接收请求。中介者在各同事间适当地转发请求以实现协作行为

优缺点

  • 将各 Colleague 解耦
  • 简化了对象协议:用 Mediator 和各 Colleague 间的一对多交互来代替多对多交互,更易于理解、维护和扩展。
  • 对对象如何协作进行了抽象:将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。
  • 使控制集中化:中介者模式将交互的复杂性变为中介者的复杂性,这可能导致中介者本身复杂而难以维护

实现

  • 简单情形下无需定义抽象中介类:当各 Colleague 仅与一个 Mediator 一起工作时,没有必要定义一个抽象的 Mediator 类
  • Colleague-Mediator 通信:当一个感兴趣的事件发生时, Colleague 必须与其 Mediator 通信,这有两种实现方法:
    • 使用 Observer(5.7) 模式,将 Mediator 实现为一个 Observer,各 Colleague 作为 Subject
      • 一旦 Subject 状态改变就发送通知给 Mediator
      • Mediator 将该事件向所有其他 Colleague 转发
    • 在 Mediator 中定义一个特殊的通知接口,各 Colleague 在通信时直接调用该接口

相关模式

Facade(4.5)与中介者的不同之处在于:

目的

在不破坏封装性的前提下捕获对象内部状态,在对象外保存该状态以备恢复

示例

一个图形编辑器,它支持图形对象间的连线。用户可用一条直线连接两个矩形,而当用户移动任意一个矩形时,这两个矩形仍能保持连接 考虑需要撤销移动命令的情形:

  • 备忘录存储内部状态:某个对象(称为原发器 originator )保存自身某个瞬间的内部状态为备忘录对象。
  • 对象可以导出内部状态为备忘录:当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。
  • 也可以依据备忘录恢复内部状态:需要恢复时,编辑器向原发器提供备忘录供其恢复内部状态

譬如计算图形对象连线的 ConstraintSolver 类,可以作为原发器:考虑取消移动的过程:

  • 作为移动操作的一个副作用,编辑器向 ConstraintSolver 请求一个备忘录;ConstraintSolver 创建并返回一个备忘录
  • 此后当用户取消移动操作时,编辑器将上次请求的备忘录送回给 ConstraintSolver
  • ConstraintSolver 改变它的内部结构以精确地将它的等式和变量返回到它们各自先前的状态

结构

  • Memento 备忘录:备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态
    • 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,
      • 管理者(caretaker)只能看到备忘录的窄接口——它只能将备忘录传递给其他对象。
      • 原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
    • 理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。
  • Originator 原发器:原发器创建一个备忘录,用以记录当前时刻它的内部状态;并使用备忘录恢复内部状态
  • Caretaker 管理者:负责保存好备忘录,但不能访问或修改备忘录的内容

管理者向原发器请求一个备忘录,保留一段时间后,在需要回退状态时将其送回给原发器

优缺点

  • 保护封装性
  • 简化了原发器:Originator 无需考虑如何保存客户请求过的各个内部状态版本,版本管理由 caretaker 完成。
  • 复制状态的开销:若生成备忘录时必须拷贝并存储大量的信息,或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销
    • 可能需要使用增量修改
  • 维护状态的开销:管理者负责删除它所维护的备忘录。然而,管理者不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理者可能会产生大量的存储开销
  • 宽窄接口:在一些语言中可能难以保证只有原发器可访问备忘录的状态。

实现

宽窄接口

备忘录有两个接口:一个为原发器所使用的宽接口,一个为其他对象所使用的窄接口。

C++中,可将 Originator 作为 Memento 的一个友元,并使 Memento 宽接口为私有的,窄接口为公共的

增量改变

如果备忘录的创建及其返回(给它们的原发器)的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变。

例如,一个包含可撤销的命令的历史列表可使用备忘录:

  • 存在一个历史列表,定义了一个特定的顺序,按照这个顺序命令可以被撤销和重做
  • 备忘录可以只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态

相关模式

Command(5.2):命令可使用备忘录来为可撤销的操作维护状态。 Iterator(5.4):如前所述,备忘录可用于迭代。

目的

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类

示例

考虑一个表示网络连接的类 TCPConnection。 一个 TCPConnection 对象的状态处于若干不同状态之一:连接已建立(Established)、正在监听(Listening)、连接已关闭(Closed)。 当一个 TCPConnection 对象收到其他对象的请求时,它根据自身的当前状态做出不同的反应。

State 模式:

  • Key:引入了一个称为 TCPState 的抽象类来表示网络的连接状态:
    • TCPState 类为各表示不同的操作状态的子类声明了一个公共接口。
    • TCPState 的子类实现与特定状态相关的行为
  • TCPConnection 类维护一个表示 TCP 连接当前状态的状态对象(TCPState 子类的实例)
    • 将所有与状态相关的请求委托给该对象
    • 一旦连接状态改变,TCPConnection 对象就会改变它所使用的状态对象

适用性

  • 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
    • State 模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化

结构

  • Context:定义面向客户的接口
    • 维护 ConcreteState 实例,表征当前状态
  • State:定义接口,封装与 Context 的特定状态相关的行为
  • ConcreteStateX:每一子类实现一个与 Context 的一个状态相关的行为

关联

  • Context 将与状态相关的请求委托给当前的 ConcreteState 对象处理。
    • Context 可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问 Context。
    • Context 是客户使用的主要接口。客户可用状态对象来配置一个 Context,一旦一个 Context 配置完毕,它的客户不再需要直接与状态对象打交道。
  • Context 或 ConcreteState 子类都可决定哪个状态是另外一个的后继者,以及是在何种条件下进行状态转换。

效果

将不同状态的行为分割开来

  • 不用 State 模式:使用数据值定义内部状态并且让 Context 操作来显式地检查这些数据。但这样将会使整个 Context 的实现中遍布看起来很相似的条件语句或 case 语句
  • 使用 State 模式:
    • 避免了庞大的条件语句
    • 每一个状态转换和动作封装到一个类中,就把着眼点从执行状态提高到整个对象的状态。这将使代码结构化并使其意图更加清晰。

确保内部状态变量的一致性:从 Context 的角度看,状态转换是原子的——只需重新绑定一个 ConcreteState 实例,而无需为一组变量分别赋值。(仅以内部数据值来定义当前状态时,其状态仅表现为对一些变量的赋值)