Xiao Peng

My personal blog moves to xiaopeng.me , blogs about design patterns will be synced to here.
肖鹏,ThoughtWorks资深咨询师,目前关注于架构模式、敏捷软件开发等领域,并致力于软件开发最佳实践的推广和应用。
多次为国内大型企业敏捷组织转型提供咨询和培训服务,在大型团队持续集成方面具有丰富的经验。
  博客园  :: 首页  :: 联系 :: 订阅 订阅  :: 管理

对Strategy模式的理解

Posted on 2006-07-02 21:37  勇敢的鸵鸟  阅读(3243)  评论(3编辑  收藏  举报

摘要:Strategy模式虽然强大,而且极其常用,但是本身没有任何神秘和高深而言。Strategy模式背后的技术就是多态,背后的思想就是封装变化。封装变化其实还隐藏了一点就是把变化的(Strategy)和不变的(Context)分离。

一.上一篇回顾

详细背景去看上一篇好了,这里只给出下面要用到的上下文。

我们拥有一个Editable接口和一个Editor接口。一个Editable代表一个可编辑的对象,同一个Editable对象可以以多种形式打开或编辑。根据Find what varies and encapsulate it的原则,变化的是文件打开的方法。所以我们把编辑器封装起来,成为Editor

 

二.Editable使用Editor,还是Editor使用Editable

在我给出Editable接口的时候,已经默认了Editable使用Editor。但是,其实还有一种选择就是Editor包含一个Editable的引用。而且后者听起来更直观:既然是Editor打开文件,应该Editor拥有这个文件。

我们设想一下,如果Editor包含一个Editable的引用。则在Editable中必然要提供一个返回filePath的函数。并且在Editor的openFile中调用该函数。类图结构如下:

 

Editor在打开文件的时候需要询问Editable文件路径,这其实违反了“Don’t ask, tell”原则。关于该原则请参考:http://idior.cnblogs.com/archive/2005/08/08/203390.html或者http://www.pragmaticprogrammer.com/ppllc/papers/1998_05.html(英文)。“Don’t ask, tell”原则简单的说就是不要根据得到的信息作决定,而是告诉别人应该做什么。以我们的例子,应该由Editable告诉Editor打开哪个文件。而不是Editor根据Editable的getFilePath的返回值作出决定。另外,设想一下,当我们扩展出StringEditable(参看前文《对Bridge模式的理解》)的时候,Editor又如何做决定呢?Editor如何知道传给自己的参数是FileEditable还是StringEditable呢?反过来,FileEditable和StringEditable却总是可以知道自己应该调用openFile还是openString,告诉它你的文件名或者你的String的内容,然后尽管让Editor去执行命令吧!

三.Strategy模式

我们先来看看什么事Strategy模式。定义一系列算法,把他们封装起来,并使他们可以互相替换。当我们决定从Editable中抽象出Editor的时候,我们实际上实现了一个Strategy模式。任何一个class只要实现了Editor的接口,都可以插入进来。 Strategy模式类图:

 
    1.Strategy解决的问题(Context)
        A.重复代码:如果不使用Strategy模式,另一个直观的方案是从Editable中派生出两种Editable实现:XMLFileEditable和TextFileEditable。可以想像,这两个类中会有大量的重复代码,比如设置文件路径,打开、关闭文件(例子中没有展示关闭文件,实际上可能还会有更多的method要重复)。 
        B.不能动态更换Editor,客户端一旦实例化Editable,相应的Editor就确定了,要想更换除非重新实例化另一个Editable。
        C.不方便的继承,Editable可能会有多种打开方式,同时也可能有多个关闭方式,这里不妨称为CloserA,和CloserB。如果我们想要同时使用XMLEditor的打开方式,而使用CloserA的关闭方式,则用继承的方式会出现XMLFileEditableWithCloserA,相应的会出现XMLFileEdtiableWithCloserB、TextEditableWithCloserA、TextEditableWithCloserB。而且很明显,任何一种继承方式,都不会避免引入重复的代码。 
        D.被拒绝的馈赠:我们使用继承很多时候必须覆盖(override)父类的一些方法,这是因为父类的实现不符合要求。这时候实际上也在暗示,该方法有不同的实现,可能Strategy模式可以帮助你解决这个问题。 
        E.类爆炸:类的数量呈指数增长。假设有N个方面(此例中的打开方式和关闭方式)可以变化,分别有ai中实现方式,则不算继承体系中间的类的个数,还可能有ai个类,如下图所示。

 

2.Strategy模式的优点 
        A.Context(Editable):可以修改、增加算法(Editor,closer),而不影响原有代码。比如,增加另一种编辑器,支持编程语言的语法高亮。 
        B.避免类爆炸:把ai个类变为1+ai个类。比如上图中可以变为下图。什么?还是四个?那是因为2×2=2+2 

        D.Client:可以动态的替换算法(相对于继承的优点)。同一个FileEditable可以在一个时刻使用XMLEditor打开,而在另一个时刻使用TextEditor打开。不得不承认,客户端增加了给对象Editable赋予行为的负担,但是个代价换来的是可以动态的修改FileEditable的行为,是不是值得需要看当时的案例。
3.何时选择使用Strategy模式,如何抽象出Strategy 
        A.代码重复:当不同类(context的子类)的代码中出现大量的重复代码时,优先考虑Strategy模式。它可能意味着这些重复的代码属于某个行为(strategy),把这个行为抽象出来。 
        B.Switch现身:代码中根据某一个条件选择执行不同的行为(strategy),可能意味着这些不同的行为应该抽象成为strategy。
        C.难以派生:你认为应该通过派生来增加功能,而又发现很多父类的行为不是你想要的。可能意味着该行为应该被抽象为strategy。 
        D.类爆炸:你的代码中出现了大量的类,这些类有着类似于XXXYYYZZZ的名字比如前面的XMLEditableWithCloserA,只是它们的反复组合。可能意味着XXX、YYY、ZZZ表示三方面的strategy。

四.总结

Strategy模式虽然强大,而且极其常用,但是本身没有任何神秘和高深而言。Strategy模式背后的技术就是多态,背后的思想就是封装变化。封装变化其实还隐藏了一点就是把变化的(Strategy)和不变的(Context)分离。

实际上很多使用了多态的地方背后都隐藏着Strategy模式。查一下你写过的代码,我敢保证你一定用过Strategy模式了。

感谢您阅读我的文章,欢迎与我交流!

Blog: http://designpatterns.cnblogs.com

Email(MSN): eagle.xiao@gmail.com

Mail list: pattern_study@yahoogroups.com; refactoring@yahoogroups.com; junit@yahoogroups.com

QQ(Designpatterns): 26227899