一、感性认识
国庆堪比春运。虽然人多,大家还是趁着难道的长假或看望亲朋好友或旅游。说到旅游,想必每一个人都会吐槽。在这大好的旅游黄金时间各个旅游景点针对不同的群体都会有不同的优惠。举个例子,某景点优惠按照一定的规则计算。
粗略的优惠根据以下的几个算法中的一个进行:
1)、儿童票半折优惠
2)、成人票无优惠
3)、老人票三折优惠
抽象策略角色类
package strategy; /** * 策略接口 定义算法的接口 * * @author wy * */ public interface TicketStrategy { /** * 算法的接口 * * @param mon * @return */ public double algorithmInterface(double mon); }
具体的策略角色类
package strategy; /** * 策略类 * 儿童景点票 * @author wangyong1 * */ public class ChildrenTicketStrategy implements TicketStrategy { @Override public double algorithmInterface(double ticketPrice) { System.out.println("儿童景点票"); return ticketPrice * (1 - 0.5); } }
package strategy; /** * 策略类 * 成人景点票 * @author wy * */ public class AdultTicketStrategy implements TicketStrategy { @Override public double algorithmInterface(double ticketPrice) { System.out.println("成人景点票"); return ticketPrice ; } }
package strategy; /** * 策略类 * 老人景点票 * @author wy * */ public class OldManTicketStrategy implements TicketStrategy { @Override public double algorithmInterface(double ticketPrice) { System.out.println("老人景点票"); return ticketPrice* (1 - 0.7); } }
上下文角色类
package strategy; /** * 购买景点票的上下文管理类 * * @author wy * */ public class TicketContext { // 持有一个具体的策略对象 private TicketStrategy strategy = null; /** * 构造方法,传入一个具体的策略对象 * * @param strategy * 具体的策略对象 */ public TicketContext(TicketStrategy strategy) { this.strategy = strategy; } /** * 购买景点票价格 * * @param mon * @return */ public double ticketPrice(double ticketPrice) { return strategy.algorithmInterface(ticketPrice); } }
客户端测试类
package strategy; public class CallerTest { private static double ticketPrice = 230.00; public static void main(String[] args) { // 选择并创建需要使用的策略对象 // TicketStrategy strategy = new AdultTicketStrategy(); TicketStrategy strategy = new ChildrenTicketStrategy(); // 创建上下文 TicketContext ticket = new TicketContext(strategy); // 计算票价 double price = ticket.ticketPrice(ticketPrice); System.out.println("购买票价 = " + price); } }
二、什么是策略模式
1)、策略模式定义
策略模式属于对象的行为模式。
其是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
2)、策略模式结构和说明
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。
策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类
策略模式涉及到三个角色:
- Strategy:通常由一个接口或抽象类实现,用来约束一系列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。
- ConcreteStrategy:具体的策略实现,包装了相关的算法或行为。
- Context:上下文,负责和具体的策略类交互,通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。
三、策略模式
1)策略模式的功能
策略模式的功能是把具体的算法实现,从具体的业务处理里面独立出来,把它们实现成为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换。
策略模式的重心不是如何来实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活、具有更好的维护性和扩展性。
2)策略模式和if-else语句
看了前面的示例,很多朋友会发现,每个策略算法具体实现的功能,就是原来在if-else结构中的具体实现。
没错,其实多个if-elseif语句表达的就是一个平等的功能结构,你要么执行if,要不你就执行else,或者是elseif,这个时候,if块里面的实现和else块里面的实现从运行地位上来讲就是平等的。
而策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文来与具体的策略类进行交互。
因此多个if-else语句可以考虑使用策略模式。
3)算法的平等性
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正是因为这个平等性,才能实现算法之间可以相互替换。
所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。
所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
4)谁来选择具体的策略算法
在策略模式中,可以在两个地方来进行具体策略的选择。
一个是在客户端,在使用上下文的时候,由客户端来选择具体的策略算法,然后把这个策略算法设置给上下文。前面的示例就是这种情况。
还有一个是客户端不管,由上下文来选择具体的策略算法,这个在后面讲容错恢复的时候给大家演示一下。
5)Strategy的实现方式
在前面的示例中,Strategy都是使用的接口来定义的,这也是常见的实现方式。但是如果多个算法具有公共功能的话,可以把Strategy实现成为抽象类,然后把多个算法的公共功能实现到Strategy里面。
6)运行时策略的唯一性
运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态的在不同的策略实现中切换,但是同时只能使用一个。
7)增加新的策略
在前面的示例里面,体会到了策略模式中切换算法的方便,但是增加一个新的算法会怎样呢?比如现在要实现如下的功能:对于公司的“战略合作客户”,统一8折。
其实很简单,策略模式可以让你很灵活的扩展新的算法。具体的做法是:先写一个策略算法类来实现新的要求,然后在客户端使用的时候指定使用新的策略算法类就可以了。
除了客户端发生变化外,已有的上下文、策略接口定义和策略的已有实现,都不需要做任何的修改,可见能很方便的扩展新的策略算法。
8)策略模式调用顺序示意图
策略模式的调用顺序,有两种常见的情况,一种如同前面的示例,具体如下:
-
- 先是客户端来选择并创建具体的策略对象
- 然后客户端创建上下文
- 接下来客户端就可以调用上下文的方法来执行功能了,在调用的时候,从客户端传入算法需要的参数
- 上下文接到客户的调用请求,会把这个请求转发给它持有的Strategy
这种情况的调用顺序示意图如图3所示:
图1 策略模式调用顺序示意图一
策略模式调用还有一种情况,就是把Context当做参数来传递给Strategy,这种方式的调用顺序图,在讲具体的Context和Strategy的关系时再给出。
四、策略模式优缺点
策略模式优点
- 定义一系列算法
策略模式的功能就是定义一系列算法,实现让这些算法可以相互替换。所以会为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。如果这一系列算法具有共功能,可以把策略接口实现成为抽象类,把这些公共功能实现到父类里面。 - 避免多重条件语句
策略模式的一系列策略算法是平等的,可以互换的,写在一起就是通过if-else结构来组织,如果此时具体的算法实现里面又有条件语句,就构成了多重条件语句,使用策略模式能避免这样的多重条件语句。
- 更好的扩展性
在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在选择使用策略的地方选择使用这个新的策略实现就好了。
策略模式缺点
- 客户必须了解每种策略的不同
比如让客户端来选择具体使用哪一个策略,这就可能会让客户需要了解所有的策略,还要了解各种策略的功能和不同,这样才能做出正确的选择,而且这样也暴露了策略的具体实现。 - 增加了对象数目
由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。 - 只适合扁平的算法结构
策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用。
对于出现需要嵌套使用多个算法的情况,比如折上折、折后返卷等业务的实现,需要组合或者是嵌套使用多个算法的情况,可以考虑使用装饰模式、或是变形的职责链、或是AOP等方式来实现。
五、策略模式总结
1)、策略模式的本质
策略模式的本质:分离算法,选择实现。
仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封装隔离带来的好处。也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关心具体的策略实现,也可以通过使用不同的实现类来实例化接口,从而实现切换具体的策略。
看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时候,会大大增加客户端使用的难度。因此,引入上下文还是很必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让整个设计模式更独立、更有整体性,也让客户端更简单。
但纵观整个策略模式实现的功能和设计,它的本质还是“分离算法,选择实现”,因为分离并封装了算法,才能够很容易的修改和添加算法;也能很容易的动态切换使用不同的算法,也就是动态选择一个算法来实现需要的功能了。
2)、对设计原则的体现
从设计原则上来看,策略模式很好的体现了开-闭原则。策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能很容易的把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。这在前面的示例中已经体现出来了,好好体会一下。
从设计原则上来看,策略模式还很好的体现了里氏替换原则。策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个父类。这样只要使用策略的客户保持面向抽象类型编程,就能够使用不同的策略的具体实现对象来配置它,从而实现一系列算法可以相互替换。
3)、何时选用策略模式
建议在如下情况中,选用策略模式:
- 出现有许多相关的类,仅仅是行为有差别的情况,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换
- 出现同一个算法,有很多不同的实现的情况,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次
- 需要封装算法中,与算法相关的数据的情况,可以使用策略模式来避免暴露这些跟算法相关的数据结构
- 出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况,可以使用策略模式来代替这些条件语句
参考:http://chjavach.iteye.com/blog/700503
由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!