技术开发 频道

策略(Strategy)模式简介



    【IT168 专稿】Strategy策略模式又名政策(Policy)模式,是属于设计模式中对象行为型模式,主要是定义一系列的算法,把这些算法一个个封装成单独的类,并且这些类(算法)可以相互替换。Strategy应用比较广泛,比如, 公司经营业务变化图, 可能有两种实现方式,一个是线条曲线,一个是框图(bar),这是两种算法,可以使用Strategy实现。图象压缩系列算法实现(不同的算法分别对应一个类)也可以采用Strategy模式实现。

    下面我将从五个方面详细介绍该模式。
    1. 意图与动机 
    当有一系统算法时,在算法的选择上,如果用if…else…则是非常不明智的,把这些算法抽象出来,然后根据程序的需要,动态地加载,这便是策略模式。定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

    2. 结构
    Strategy的结构如下图所示:


下面简要介绍一下以上类图中各个类的作用:

    1) Context类为上下文类,负责维护和更新算法所要处理的环境和数据。譬如对于图象压缩来说,Context负责数据的提取、压缩参数的获得以及压缩以后善后工作等等;
    2) Strategy代表抽象的算法,譬如对于图象压缩来说,它代表的就是图象压缩算法,至于具体那种压缩?JEPG压缩还是GIF压缩,Strategy类不关心,它只为具体的图象压缩算法提供统一的接口;
    3) ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC则代表特别具体的算法。
Context类维护Strategy的一个引用(或者指针)。一旦Context所维护的上下文有变化(譬如输入数据有变化),它就将这个职责转发给它的Strategy对象。Context的客户制定应该使用哪一种Strategy(算法)的方式是直接将它想要的具体的Strategy  (ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC……)装入Composition中。
    3. 实用性
    当存在一下情况时,适合使用Strategy模式
    1) 许多相关的类仅仅是行为上的差异。“策略”提供了一种用多个行为中的一个行为类配置一个类的方法;
    2) 需要使用一个算法的不同的变体。例如,您可能会定义一些反映不同空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式;
    3) 算法使用客户不应该知道的具体的实现步骤,可以使用策略模式以免暴露复杂的、与算法相关的数据结构;
    4) 一个类定义了多种行为,并且为这些行为在这个类的操作中以多个条件语句的形式出现相关的条件分支移入它们各自的Strategy类中,以代替这些语句。



    4. 具体的实例
    策略模式与状态模式非常相似,都是将一组对象抽象出来,与状态模式不同的时,策略模式抽象的算法,而状态模式抽象的是对象内部的状态。因此在实现上它们也非常类似。下面我用Java语言写一个简单的例子。
    程序清单1:Context类
package designpatterns.strategy; import java.util.*; import java.io.*; /** * 该类是策略模式中的Context类,用来管理算法运行的上下文环境 * @author Feifan Yin * */ public class Context { private Strategy m_strategy; private byte[] inData; private byte[] outData; public Context(Strategy pStrategy, File file){ m_strategy = pStrategy; try{ FileInputStream fi = new FileInputStream(file); }catch(Exception e){ e.printStackTrace(); } } public void contextInterface(){ if(inData != null){ this.outData = m_strategy.algorithmInterface(inData); } } void printResult(){ if(this.outData != null){ System.out.println(new String(outData)); } } public static final void main(String arg[]){ //选择一个算法 Strategy strategy = new ConcreateStrategyA(); //Strategy strategy = new ConcreateStrategyB(); //Strategy strategy = new ConcreateStrategyC(); //给出上下文环境 Context context = new Context(strategy,new File("in.txt")); //利用上下文环境驱动算法执行 context.contextInterface(); //输出算法执行结果 context.printResult(); } }
    程序清单2:Strategy类
package designpatterns.strategy; /** * 表示一个抽象的算法,为具体的算法定义皆空 * @author Feifan Yin * */ public abstract class Strategy { /** * 抽象的算法接口 * @param data 输入数据 * @return 输出数据 */ public abstract byte[] algorithmInterface(byte[] data); }
    程序清单3:ConcreateStrategyA类
package designpatterns.strategy; /** * 算法封装类,表示一个具体的算法 * @author Feifan Yin * */ public class ConcreateStrategyA extends Strategy{ /** * 具体算法的实现 */ public byte[] algorithmInterface(byte[] data) { // TODO Auto-generated method stub for(int i = 0;i<data.length; ++i){ data[i] = (byte) ~data[i]; } return data; } }
    程序清单4:ConcreateStrategyB类
package designpatterns.strategy; /** * 算法封装类,表示一个具体的算法 * @author Feifan Yin * */ public class ConcreateStrategyB extends Strategy{ /** * 具体算法的实现 */ public byte[] algorithmInterface(byte[] data) { // TODO Auto-generated method stub for(int i = 0;i<data.length; ++i){ data[i] = (byte) (6 ^ data[i]); } return data; } }

    程序清单5:ConcreateStrategyC类
package designpatterns.strategy; /** * 算法封装类,表示一个具体的算法 * @author Feifan Yin * */ public class ConcreateStrategyC extends Strategy{ /** * 具体算法的实现 */ public byte[] algorithmInterface(byte[] data) { // TODO Auto-generated method stub for(int i = 0;i<data.length; ++i){ data[i] = (byte) (123 & data[i]); } return data; } }


    5. 效果

    Strategy模式具有以下优点:
    1) 相关算法系列:Strategy类层次为Context定义了一系列的可重用的算法或者行为。继承有助于析取算法中的公共功能,例如参数合法性验证,可以放在Strategy类层次;
    2) 消除一些条件语句:Strategy模式提供了用体检语句选择所需的行为以为的另外一种选择。当不同的行为堆砌在一个类中,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句;
    3) 选择合适的实现:Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求不同策略中进行选择。
    然而世界上没有完美的事情,Strategy模式在带来方便的同时,也存在如下的一些缺点: 
    1) 客户必须了解不同的Strategy:这是本模式潜在的一个缺点,其实客户要选择一个合适的Strategy就必须知道这些Strategy有何不同。这不可避免地向客户暴露具体的实现问题。
    2) Strategy和Context之间的通信开销:无论各个ConcreteStrategy实现算法是简单还是复杂,它们都共享Strategy定义的接口。因此,很有可能具体的ConcreteStrategy(有些ConcreteStrategy可能非常简单,不需要太多的参数)不会用到通过这个接口传递的某些信息,这样就会造成信息的浪费;
    3) 增加了对象的数目:Strategy增加了一个应用中的对象的数目(譬如有多个上下文的时候,每个上下文中都存在Strategy对象)。有时候你可以将Strategy实现为可供各个Context共享的无状态的对象来减少这以开销。

    6. 总结
    本人曾经参与了总装某个型号任务的开发,整个项目中有许多地方可以用到Strategy模式。然而非常可惜的是,由于时间紧急,许多开发人员仓促上阵,只注重具体细节的实现,并没有把这些细节中的共同的东西抽象出来,造成了项目臃肿,代码量非常大,而且耦合非常紧密,导致Bug丛生。好在后来我们意识这一点,对代码进行了Review,调整了代码结构,其中就包括应用Strategy等许多设计模式,最终还是取得了成功。
0
相关文章