“设计模式”学习之九:策略、模板方法与访问者(行为型)

一、策略(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模式定义的对象结构进行操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值