第23讲:Strategy 策略模式
2006.9.25 李建忠
算法与对象的耦合
对象可能经常需要使用多种不同的算法,但是如果变化频繁,会将类型变得脆弱……
动机(Motivation)
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
意图(Intent)
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户而变化。
——《设计模式》GoF
例说Strategy模式应用
这个程序有两个可能的变化点:当枚举类型增加时,即处理的方法增加,那么Process函数需要修改补充一个if else分支;当我们想对分支1的处理ProcessA进行更改时,也要对Process函数进行修改。
针对上面的问题,我们首先想到的是把ProcessA写成受保护的虚函数(在OO中我们一般把虚函数都写成受保护的函数,因为它是能改变类的行为的函数,一般情况下只应该作为子类和父类之间的协议出现)。
Strategy模式的设计
把Cart类和ProcessStrategy类作为对象组合的方式使用。IProcessStrategy表达的是一个算法抽象。
抽象和具体算法
客户程序
这样算法就可以动态地去改变了,我们动态地去new一组具体的Process算法,然后提供给Cart类使用。
delegate只要符合参数和返回值,不管是静态方法或者是抽象方法,就可以动态地挂上。但是接口需要抽象含义一致,因此对于这个模式更推荐使用接口来表达抽象的算法。
结构(Structure)
算法并不是孤立的,它通常都需要有一些上下文去调用它,或者是传入一些参数。Strategy类型里面不携带状态信息(这是与模板方法的区别,模板方法本身是携带状态信息的),我们不能把它看成一种实例,即使有状态,也是会通过参数传入。一个Strategy定义了一个算法的完整步骤和结构,只要用一个Strategy具体类,就可以完成整个算法的操作,不会有其它依赖和耦合。Context和Strategy是一个对象组合的使用关系。Strategy中的抽象接口随时可以替换成具体的类,达到在不同算法之间动态地切换。
这个模式的核心是通过对象组合的方式把本来直接调用的内容委托给接口实体对象来完成,至于接口实体对象具体是什么,在运行时才知道,即是运行时改变。
Strategy模式的几个要点
Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换,所谓封装算法,支持算法的变化。
Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
与State类似,如果Strategy对象没有实例变量,那么各个上下文可以共享一个Strategy对象,从而节省对象开销。
Strategy模式适用的是算法结构中整个算法的改变,而不是算法中某个部分的改变。
Template Method方法:执行算法的步骤协议是本身放在抽象类里面的,允许一个通用的算法操作多个可能实现
Strategy模式:执行算法的协议是在具体类,每个具体实现有不同通用算法来做。
.NET框架中的Strategy应用
例如ArrayList类,Sort方法做不了什么事情,因为Point类不支持排序,没有继承IComparer接口。
但如果我们想支持排序,其实Sort方法就是一个Strategy模式,它支持传入一个继承IComparer接口的具体类。
IComparer接口其实就是Strategy我们上面例子中的IProcessStrategy。我们可以实现一个IComparer接口,然后把具体类传入,这样Sort方法就会按我们定义的排序规则执行了。
当我们需要切换排序方法的时候,只需要更改传入Sort的具体比较类即可。
ArrayList虽然没有把排序方法作为字段组合,但是它把它作为参数来使用了。因为比较不是在ArrayList中大多数方法都使用,只有排序才需要使用,所以把它作为参数使用更合适。
2010.10.28