例说策略模式(Strategy Pattern)

来源:互联网 发布:windows查看cpu命令 编辑:程序博客网 时间:2024/06/05 00:27
策略模式
      这个模式被HEAD FIRST 设计模式放到了第一章,重要性可见一斑,类似于日常理解的“委托"概念,客户发布了一个需求,任何能满足这个需求的都可以来争取成为"代理人",通过代理人完成具体的操作,如果代理人在工作时被因故撤职,客户会选择其它代理人完成操作,李某某案基本使用的就是这种模式,算了,换个例子,以政府和承包商之间的关系,具体阐明下策略模式到底是个啥。
     本文里的政府以铁道部(原)为例,在建国之初的中国铁路建设一张白纸,铁道部想要按照上级要求建造新中国铁路网,于是发布了一个公告,兹准备修铁路,任何符合以下条件要求的单位或个人请来竞标。具体要求如下:
     1) 会修建站台
     2) 会建造新车皮
     3) 会修建铁轨
     4) 会设计信号灯
     5) 会日常维护
     于是,一张附有要求的清单被贴了出来,内容如下                                           

      当时有三家厂商(A、B、C)能够完成这些要求,于是前来竞标,各自带着自己的竞标方案(具体实现方式)前来,政府也成立了专门的办公室(RallwayConstructable 类型实例变量)负责相关业务,将竞标成功的厂商作为官方指定(set方法),并且执行官方指定厂商的设计方案(perform)方法,于是形成了下面一种关系
                             
这种基本关系中存在三方
      a) 甲方,政府,也称客户方,铁道部(原)接到上级的要求和预算后来做这件事,他要求新建站台、车皮、铁轨、信号灯及维护事宜,具体怎么做,使用什么技术方案和手段来做。政府不重点关注,他关心的是谁能够以最优策略完成这件事(实际开发中客户方需要了解每个策略算法的异同以供做出选择),在预算范围内保质保量完成且维护良好,这样向上级好汇报,可见甲方的关注点是RallwayConstructable,不管哪个厂商,只要能低价高质的完成这个关注点,就选谁。
      Government类中RallwayConstructable类型变量代表的是被选中的承包商基类变量,set方法表示选定某个承包商,perform方法类似于验收执行,调用的是官方指定承包商的具体方案。
      b) 关注接口层,这个是策略模式中比较重要的组件,以一个接口表示要求,政府要把修建站台、铁轨等这些繁杂的事情委托给承包商去做,首先要提要求,类似以规范一样下发给所有潜在承包商,如果你能够实现这些规范里的所有要求,政府不关心你是A/B/C,一视同仁的视为RallwayConstructable对象,并在选中某厂商后执行他的具体操作。
      RalllwayConstructable 接口类似于资格证书,拥有该证书的才有资格参与竞标,Government类中的实例变量是RallwayConstructable类型而非某个具体厂商类型,是因为政府关注点在资格上且可能随时更换厂商,这就是设计模式中的一个重要原则——面向接口、而非面向实现的编程。
      c) 乙方,承包商,也成执行方,众多厂商为了能够具备竞标资格,需实现RallwayConstructable接口,每个厂商的实现方式不同,采用何种技术手段来修建站台、铁轨,建造车皮及维护等,具体的执行是厂商类中需要关注的地方,因为在甲方眼里他们不是vendor而是RallwayConstructable,即使被选中也面临着被替换的风险。
     具体代码如下:
RailwayConstructable.java
RailwayConstructable.javapackage com.klpchan.strategypattern;    //承包商所必须具备的能力    public interface RailwayConstructable {    //修建站台    public void buildStation();    //制造车皮    public void createTrain();    //修筑铁轨    public void buildTrack();    //设计信号灯    public void designSignal();    //日常维护    public void maintain();}
Government.java
package com.klpchan.strategypattern;public class Government {    //将修建铁路行为委托给一个承包商,具体怎么做由承包商决定    RailwayConstructable mRailwayConstructor;    //执行修建站台业务    public void  performBuildStation() {        mRailwayConstructor.buildStation();    }    //执行建造车皮业务    public void performCreateTrain() {        mRailwayConstructor.createTrain();    }    //执行建造铁轨业务    public void performBuildTrack() {        mRailwayConstructor.buildTrack();    }    //执行建立信号灯业务    public void performDesignSignal() {        mRailwayConstructor.designSignal();    }    //执行后期维护业务    public void performMaintain() {        mRailwayConstructor.maintain();    }    public RailwayConstructable getRailwayConstructor() {        return mRailwayConstructor;    }    //选择一个具有指定要铁路建造能力的承包商    public void setRailwayConstructor(RailwayConstructable mRailwayConstructor) {        this.mRailwayConstructor = mRailwayConstructor;    }}
VendorA.java
package com.klpchan.strategypattern;    //承包商A因实现了指定接口,因此具备参与竞标的要求。可供政府选择。    public class VendorA implements RailwayConstructable{    //承包商A建造站台的方法实现,每个承包商都会有自己的方法,必须要实现是政府所要求。    @Override    public void buildStation() {        // TODO Auto-generated method stub        System.out.println("vendor A build the station!");    }    @Override    public void createTrain() {       // TODO Auto-generated method stub       System.out.println("vendor A create the train!");    }    @Override    public void buildTrack() {       // TODO Auto-generated method stub       System.out.println("vendor A build the track!");    }    @Override    public void designSignal() {       // TODO Auto-generated method stub       System.out.println("vendor A design the signal light!");    }    @Override    public void maintain() {       // TODO Auto-generated method stub       System.out.println("vendor A maintain!");    }}
      其它厂商B/C和A类似,只是把打印语句中的A换成对应名即可,Government类设置某个厂商类后,进行perform操作,可以在运行时动态切换厂商类,此时的厂商类本质上已经是具体实现的算法策略,不同的厂商有着不同的策略,客户有很多策略可供选择,选中后使用的就是该策略提供的方法,一个策略包括了所有协议里规定要求的具体实现,众多厂商形成就是众多策略,形成了策略簇,用户可以随时改变已选策略。这就引出了算法策略的官方定义,如下:
      定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
      官方定义中的算法族就是众多厂商所提供的的策略族,它们之间可以互相替换,且政府不关心厂商是否改变具体实现步骤,政府相信猫论,厂商负责实现。
延伸
      随着相关技术的发展,术业有专攻,铁道部发现厂商A建造站台能力不错,但制造车皮的能力较差,厂商B在建造铁轨方面颇有心得,但对信号设计一窍不通,政府为了资源整合,取长补短,把具体的要求项目细化,形成了一份新的协议分拆成五分,众多厂商也放弃了自己并不擅长的领域,转而集中有限精力进行专项能力提升,从而形成了下面的局面:                                                                   
         
       政府为每个分项单独设立了协议要求,厂商A在站台建设及车皮建造方面有实力,厂商B和C也有自己实现的接口,这样的政府在完成每个任务时都单独进行招标,如修建站台时主要对VendorA和VendorB进行考核,这样的好处是完善了资源利用,对于每个细分要求都有针对性极强的算法族,代码清晰整洁,缺点是需要很多厂商算法类,实际上本图为了简易将待选策略的名字都命名为VendorA,VendorB等,实际开发中不应这样,应单独表明某个具体用途的算法类名称,如VendorA实现了StationBuilder和TrainCreater接口,为了代码清晰整洁,应该分拆了两个类分别实现对应的接口,类名可以根据需求取类似于StationBuildVendorA和TrainCreaterVendorA以示区别。
      该延伸关系更符合实际,也使用了设计模式中的另一个重要原则——多用组合、少用继承,组合与继承较为重要的区别是该协议是否为所有子类所实现,应用到本例中可以这样理解,政府并没有傻到每次兴建铁路都需要把五项措施全做一遍的地步,如果以Government为父类,以地方铁路局为子类,上海铁路局可能关注更多的是高尖端技术如信号灯设计和日常维护,而基建相对落后的兰州铁路局侧重于站台建设及铁轨铺设,子类只需要组合自己感兴趣的关注点接口即可,而不用像采用继承关系那样每个接口都重写实现,增加了灵活性和动态修改能力。
     实践学习到此结束,理论部分内容简单整理如下:
结构图
 
参与者
     Strategy(策略,上图中的Compositor) ,定义了所有算法的公共接口。
     ConcreateStrategy(具体策略,以Strategy接口实现某些具体算法)
     Context 维护一个对于Strategy对象的引用,使用ConcreteStrategy来配置。
适用场景
    a) 众多策略类仅是行为有所不同,“策略”实现用多个行为中的一个行为来配置方法
    b) 需要实现一个算法的不同变体时,考虑空间/时间权衡的算法
    c)一个类中通过if-else组织起来的同类型不同行为。
效果
    + 相关算法系列,Strategy与具体算法间的继承关系,有利于析出算法族的公共部分。
    + 组合替代继承,上文已解释,通过组合增加可维护、可扩展性,且可以动态改变具体算法。
    + 消除代码if-else语句,含有较多条件语句可考虑用该模式——千万别犯模式病,看到if/else就像策略模式。
    + 实现选择,客户根据时间/空间性能权衡取舍选择不同策略实现。
    -  客户需了解策略之间的异同,具体算法可暴露整体解决思路给客户,但具体算法与数据结构不宜暴露。
    -  Context与Strategy的通信开销,不同策略使用不同数据结构,有可能造成部分对象的无效初始化。
    -  增加对象数目,以致产生大量具体算法类。
源代码场景举例
     在Android开发中应用较为普遍的setOnClickListener方法,就用到了策略模式,系统框架层写好了特定的控件如按钮等,需要开发者为其设置监听器,而任意监听器都有个onClick方法,这就是算法,控件代码中存有个onClickListener对象,在控件被点击时调用选定监听器的onClick方法,在不同的上下文环境下还可以动态改变监听器,这是策略模式的典型应用。
 
小结
      本文以政府和承包商的例子,阐述了策略模式的整体思想和具体内容,说明了设计模式中的两个重要原则及实现,出于简洁表示的目的,代表具体策略的厂商类我是用vendor来命名,实际开发中需针对不同协议拆分成对应算法类,作为对象行为型的策略模式,给出代码实例,测试运行较为简单所以未贴出结果,有兴趣可自行实现,国庆Happy~~
 
     ~~版权所有~转载请注明~~   
原创粉丝点击