策略模式
这个模式被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.java
package 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~~
~~版权所有~转载请注明~~