一、策略(Strategy,别称“政策Policy”)
1、引言
对于某个功能子系统,在不同情况下可能需要不同的算法,并且运行时算法可切换。这时需要Strategy模式,它封装一个个可互相替换的算法,并且可以避免向客户程序员暴露与算法相关的复杂数据结构(高内聚、低偶合High Cohesion & Low/Loose coupling)。与State模式一样,它可以替代程序中的大量如switch/case的条件语句。
2、思路
《设计模式》给出了如下典型类图。其中,上下文Context维护一个策略对象Strategy,并将职责ContextInterface()转发给具体策略对象ConcreteStrategyA/B的AlgoInterface()。而ConcreteStrategyA/B便是封装好的可互相替代的算法。
Context对象可以将Strategy所需的数据作为ContextInterface()的参数进行传递从而实现解耦,也可以将自身作为参数进行传递或由Strategy引用Context。(推荐前者)
3、引申
参考http://www.cnblogs.com/justinw/archive/2007/02/06/641414.html,分析《Head First》中的“会飞的橡皮鸭子”实例,回顾一些设计准则:
(1)Identify theaspects of your application that vary and separate them from what stays thesame.(Encapsulates what varies封装变化):
众所周知,并不是所有的鸭子都会飞、会叫。fly()和quack()行为总是不老实,而swim()行为是很稳定的,这个行为是可以使用继承来实现代码重用。而fly()和quack()行为分别拿出来封装成FlyBehavior和QuackBehavior接口。
(2)Programto an interface, not an implementation.(面向接口编程,而不要面向实现编程):
在面向接口编程的时候,可以使用多态,那么实际运行的代码只依赖于接口(interface,抽象类,基类),而不管这些接口提供的功能是如何实现的(由具体子类实现)。故在策略模式中,Context维护一个抽象类策略Strategy的对象。
最后看看类图:
4、应用提示
(1)Strategy模式的一个潜在缺点在于,客户必须在知道不同ConcreteStrategyA/B的作用,才能进行合适的选择。另外,因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。
(2)ConcreteStrategyA/B对象通常是很好的轻量级对象,可用Flyweight模式共享。
二、模板方法(Template Method)
1、引言
模板方法属于类模式,利用继承多态特性来实现。它是将算法的逻辑框架(即算法中的步骤及其先后顺序)放在抽象基类中,同时定义好细节的接口,并在子类中实现细节。
回顾一下Strategy模式:它是将算法中可能变化的细节步骤,抽取封装成类,并由算法类通过引用委托细节类来实现。
2、思路
如下典型类图中,抽象类AbstractClass中实现一个模板方法TemplateMethod(),当中定义了算法中的各个步骤(包含类似Operation3()的固定步骤、类似PrimitiveOperation1/2()的细节变化步骤);子类ConcreteClass1/2重用固定的步骤、重写当中变化的细节步骤(即原语操作Primitive Operation)。
3、引申
模板方法模式简单明了、且应用非常广泛。它会导致一种反向的控制结构,当中涉及一个设计准则:
GoF称其为“好莱坞法则”——“别找我们,我们找你don’t call us, we’ll call you”,指的是父类调用一个子类的操作,而不是相反。
另一种说法是“依赖倒置原则”——DIP(DependencyInversion Principles),指的是父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模块),低层模块反而要依赖高层模块。
各种具体准则的知识,可以参看:http://www.iteye.com/news/20930
4、应用提示
(1)C++实现模板方法模式时,可以将原语操作(细节算法)定义为保护(Protected)成员,这样可以保证只被模板方法调用(当然子类也可以)。 模板方法自身不被重定义,故可定义为非虚成员函数。
(2)工厂方法Factory Method模式经常被模板方法调用,即模板方法调用原语操作(当中含有语句new **)。
三、访问者(Visitor)
1、引言
访问者用于表示一些作用于某个对象群中各个对象元素的操作。当需要变更或更新对元素的操作时,使用访问者可以避免修改元素本身。
2、思路
如下典型类图中,ObjectStructure对象结构就是之前所谓的对象群,具体包括:组合模式中的component结构(包含node-leaf的树形结构)、集合(可能含不同类型结构的成员)。对于被访问的元素对象Element(或称Visitable、如Node)而言,它能够通过Accept()来接受特定的访问者ConcreteVisitor1/2,并从而调用访问者ConcreteVisitor1/2的访问操作VisitConcreteElementA/B()实现访问其自身。最后,由访问者调用各个元素的OperationA/B()实现对不同元素对象的具体操作。
3、引申
Visitor模式允许在不破坏类的前提下有效增加作用其上的新操作。实现的关键在于双分派(Double-Dispatch)技术,它意味着最终得到执行的操作取决于两个类型:Visitor和Element。上图中的Accept()是典型的双分派操作,在C++语言(支持单分派)中,往往包含如下代码:
void ConcreteElement?::Accept(Visitor*vis)
{
cout<<"visiting ConcreteElement?..."<<endl;
vis->VisitConcreteElement?(this);
}
那么,最终会执行哪个操作VisitConcreteElement?呢?一方面,取决于当前Element的具体类型ConcreteElement?;另一方面,取决于传入的参数Visitor*vis属于ConcreteVisitor?。这当中可能涉及到C++的RTTI(运行时类型识别:Runtime type identification)。传入的参数为Element*参数 ,然后用RTTI决定具体是哪一类的ConcreteElement参数
4、应用提示
(1)前面提到,Visitor模式非常适合对在不“污染”现有元素的前提下添加新的操作。那么,如果对象结构类中的元素经常变动,则不适合使用Visitor模式。因为这需要对所有访问者(即Visitor及其子类)都添加一些操作进行重定义,代价高昂。
(2)Visitor模式另一个为人诟病之处在于,元素ConcreteElement不得不向访问者ConcreteVisitor提供访问其内部状态的public公共接口,可能会破坏元素的封装性。
(3)Java中的Collection技术(包括Vector和Hashtable)应用广泛,它们是典型的集合对象群,内部可以含不同类型的成员。Java的reflect反射技术使得Visitor模式能够适用于(1)中提到的元素经常变动场合。Reflect技术是在运行期间动态获取对象类型和方法的一种技术,具体实现可参考:http://www.javaworld.com/javaworld/javatips/jw-javatip98.html. 提供一个不错的相关博文链接:http://www.newsmth.net/pc/pcshowcom.php?cid=142555。
(4)Visitor模式可以用于定义解释Interpreter模式中的解释操作Interpreter(),也可用于对组合Composite模式定义的对象结构进行操作。