摘要: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