C++设计模式-第三篇
本章内容:
1 备忘录模式
2 状态模式
3 组合模式
4 迭代器模式
5 职责模式
6 命令模式
7 访问器模式
8 解析器模式
1 备忘录模式(Memento Method模式)
** “状态变化”模式:**
在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维护高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
典型模式:
1). Memento Method
2). State动机(Motivation)
在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
模式定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后可以将该对象恢复到原先保存的状态。
-
结构(Structure)
要点总结
备忘录(
Memento
)存储原发器(Originator
)对象的内部状态,在需要时恢复原发器状态。Memento
模式的核心是信息隐藏,即Originator
需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memento
)。由于现代语言运行时,(如java、c#等)都具有相当的对象序列化支持,因此往往采用效率较高,又较容易正确实现的序列化方案来实现
Memento
模式。
2 状态模式(State模式)
** “状态变化”模式:**
在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维护高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
典型模式:
1). Memento Method
2). State动机(Motivation)
在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为可能完全不同。
如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?
模式定义
允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。
-
结构(Structure)
要点总结
State
模式将所有与一个特定状态相关的行为都放入一个State
的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State
的接口,这样实现了具体操作与状态转换之间的解耦。为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换时原子性的--即要么彻底转换过来,要么不转换。
如果
State
对象没有实例化变量,那么各个上下文可以共享同一个State
对象,从而节省对象开销。
3 组合模式(Composite模式)
** “数据结构”模式:**
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
典型模式:
1). Composite
2). Iterator
3). Chain of Responsibility动机(Motivation)
在软件某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
模式定义
将对象组合成树形结构以表示“部分-整体”的层次结构。
Composite
使得用户对单个对象和组合对的使用具有一致性(稳定)。-
结构(Structure)
要点总结
Composite
模式采用树形结构实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器。将“客户代码与复杂的对象容器结构”解耦是
Composite
的核心思想,解耦之后,客户代码将与纯粹的抽象接口--而非对象容器的内部实现结构--发生依赖,从而更能“应对变化”。Composite
模式在具体的实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历要求,可使用缓存技巧来改善效率。
4 迭代器模式(Iterator模式)
** “数据结构”模式:**
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
典型模式:
1). Composite
2). Iterator
3). Chain of Responsibility动机(Motivation)
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。
使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。
模式定义
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。
-
结构(Structure)
要点总结
迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表。
迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。
5 职责链模式(Chain of Responsibility模式)
** “数据结构”模式:**
常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大地破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案。
典型模式:
1). Composite
2). Iterator
3). chain of Responsibility动机(Motivation)
在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显式指定,将必不可少地带来请求发送者与接受者的紧耦合。
如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
模式定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
-
结构(Structure)
要点总结
Chain of Responsibility
模式的应用场合在于“一个请求可能有多个接受者,但是最后真正的接受者只有一个”,这时候请求发送者与接受者的耦合有可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好地应对变化。应用了
Chain of Responsibility
模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。
6 命令模式(Command模式)
** “行为变化”模式:**
在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。
典型模式:
1). Command
2). Visitor动机(Motivation)
在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将“行为请求者”与“行为实现者” 解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
模式定义
将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
-
结构(Structure)
要点总结
Command
模式的根本目的在于将“行为请求者”与“行为实现者” 解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。实现
Command
接口的具体命令对象ConcreteCommand
有时候根据需要可能会保存一些额外的状态信息。通过使用Composite
模式,可以将多个“命令”封装为一个“复合命令”MacroCommand
。Command
模式与C++
中的函数对象有些类似。但两者定义行为接口的规范有所区别:Command
以面向对象中的“接口-实现”来定义行为接口规范,更严格,但有性能损失;C++
函数对象以函数签名来定义行为接口规范,更灵活,性能更高。
7 访问器模式(Visitor模式)
** “行为变化”模式:**
在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。
典型模式:
1). Command
2). Visitor动机(Motivation)
在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
模式定义
表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
-
结构(Structure)
要点总结
Visitor
模式通过所谓双重分发(double dispatch
)来实现在不更改(不添加新的操作-编译时)Element
类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。所谓双重分发即
Visitor
模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept
方法的多态辨析;第二个为visitElementX
方法的多态辨析。Visitor
模式的最大缺点在于扩展类层次结构(增添新的Element
子类),会导致Visitor
类的改变。因此Visitor
模式适用于“Element
类层次结构稳定,而其中的操作却经常面临频繁改动”。
8 解析器模式(Interpreter模式)
** “领域规则”模式:**
在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案。
典型模式:
1). Interpreter动机(Motivation)
在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。
在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
模式定义
给定一个语言,定义它文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
-
结构(Structure)
要点总结
Interpreter
模式的应用场合是Interpreter
模式应用中的难点,只有满足“业务规则频繁变化,且类似的结构不断重复出现,并且容易抽象为语法规则的问题”才适合使用Interpreter
模式。使用
Interpreter
模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。Interpreter
模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter
模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具。