策略模式-设计模式
在讲述之前,我们首先看小例子:
现实生活中我们去商场上买东西的时候,卖场经常根据不同的客户来制定不同的报价策略,比如新客户不打折扣,针对老客户打9折,针对VIP打8折……
现在我们做一个报价管理模块,简要点就是针对不同的客户,提供不同的报价。
假如是有你来做,你会怎么做?
在日常的开发中,我们大部分会写出如下的代码片段:
public class QuoteManager { public BigDecimal quote(BigDecimal originalPrice,String customType){ if ("新客户".equals(customType)) { System.out.println("抱歉!新客户没有折扣!"); return originalPrice; }else if ("老客户".equals(customType)) { System.out.println("恭喜你!老客户打9折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; }else if("VIP客户".equals(customType)){ System.out.println("恭喜你!VIP客户打8折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } //其他人员都是原价 return originalPrice; } }
我们会在日常的开发中大量使用if ,else if,else if,else……上面的代码工作的很好,但是呢,经过大量的判断操作,也是有问题的,将不同客户的报价的算法都放在了同一个方法里面,使得该方法随着业务的拓展会变得越来越臃肿,时刻更改着此处代码,达不到复用性。
放看一下上面的改进,我们将不同客户的报价算法单独作为一个方法。
public class QuoteManagerImprove { public BigDecimal quote(BigDecimal originalPrice, String customType){ if ("新客户".equals(customType)) { return this.quoteNewCustomer(originalPrice); }else if ("老客户".equals(customType)) { return this.quoteOldCustomer(originalPrice); }else if("VIP客户".equals(customType)){ return this.quoteVIPCustomer(originalPrice); } //其他人员都是原价 return originalPrice; } /** * 对VIP客户的报价算法 * @param originalPrice 原价 * @return 折后价 */ private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) { System.out.println("恭喜!VIP客户打8折"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } /** * 对老客户的报价算法 * @param originalPrice 原价 * @return 折后价 */ private BigDecimal quoteOldCustomer(BigDecimal originalPrice) { System.out.println("恭喜!老客户打9折"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } /** * 对新客户的报价算法 * @param originalPrice 原价 * @return 折后价 */ private BigDecimal quoteNewCustomer(BigDecimal originalPrice) { System.out.println("抱歉!新客户没有折扣!"); return originalPrice; } }
上面的代码比刚开始的时候好一点,将每个具体的算法都单独抽出来,当某个具体的算法出现变动问题,只需要修改相应的算法就可以啦。
但是改进后的代码还是有问题的,有什么问题呢?
当我们新增一个客户类型,首先要添一个该客户类型的报价算法,然后再quote方法中再次添加一个else if ,这就违反了设计原则--开闭原则(open-closed - principle)
开闭原则:
对于扩展是开放的,这意味着模块的行为是可以拓展的,当应用的需求改变时候,可以对模块进行扩展。
对于修改是关闭的,对模块行为进行扩展时 ,不用改动模块的源代码或者二进制代码。
那有没有办法可适用于可扩展,可维护,又可以方便的响应变化呢,下面就是我们要讲的策略模式。
策略模式
定义:
策略模式定义了一系列的算法,把它们一个个封装起来,并且使他们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
结构:
- 策略接口角色IStrategy:用来约束一系列具体策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体实现的算法。
- 具体策略实现角色ConcreteStrategy:具体的策略实现,即算法的具体实现。
- 策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现了交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从中获取相关数据,回调了策略上下文对象的方法。
UML类图
策略模式代码的一般实现
package strategy.examp01; //策略接口 public interface IStrategy { //定义的抽象算法方法 来约束具体的算法实现方法 public void algorithmMethod(); }
具体的策略实现:
package strategy.examp01; // 具体的策略实现 public class ConcreteStrategy implements IStrategy { //具体的算法实现 @Override public void algorithmMethod() { System.out.println("this is ConcreteStrategy method..."); } }
package strategy.examp01; // 具体的策略实现2 public class ConcreteStrategy2 implements IStrategy { //具体的算法实现 @Override public void algorithmMethod() { System.out.println("this is ConcreteStrategy2 method..."); } }
package strategy.examp01; /** * 策略上下文 */ public class StrategyContext { //持有一个策略实现的引用 private IStrategy strategy; //使用构造器注入具体的策略类 public StrategyContext(IStrategy strategy) { this.strategy = strategy; } public void contextMethod(){ //调用策略实现的方法 strategy.algorithmMethod(); } }
客户端使用:
package strategy.examp01; //外部客户端 public class Client { public static void main(String[] args) { //1.创建具体测策略实现,多态使用 IStrategy strategy = new ConcreteStrategy2(); //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中 StrategyContext ctx = new StrategyContext(strategy); //3.调用上下文对象的方法来完成对具体策略实现的回调 ctx.contextMethod(); } }
针对一开始所讲的例子:可以使用策略模式对其进行改造,不同的类型的客户有不同的折扣,可以将不同类型的客户报价规则都封装为一个独立的算法,然后抽象出报价算法的公共接口。
公共报价的策略接口:
package strategy.examp02; import java.math.BigDecimal; //报价策略接口 public interface IQuoteStrategy { //获取折后价的价格 BigDecimal getPrice(BigDecimal originalPrice); }
新客户报价策略实现:
package strategy.examp02; import java.math.BigDecimal; //新客户的报价策略实现类 public class NewCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("抱歉!新客户没有折扣!"); return originalPrice; } }
老客户报价策略实现:
package strategy.examp02; import java.math.BigDecimal; //老客户的报价策略实现 public class OldCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("恭喜!老客户享有9折优惠!"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
VIP客户报价策略实现:
package strategy.examp02; import java.math.BigDecimal; //VIP客户的报价策略实现 public class VIPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("恭喜!VIP客户享有8折优惠!"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
报价上下文:
package strategy.examp02; import java.math.BigDecimal; //报价上下文角色 public class QuoteContext { //持有一个具体的报价策略 private IQuoteStrategy quoteStrategy; //注入报价策略 public QuoteContext(IQuoteStrategy quoteStrategy){ this.quoteStrategy = quoteStrategy; } //回调具体报价策略的方法 public BigDecimal getPrice(BigDecimal originalPrice){ return quoteStrategy.getPrice(originalPrice); } }
外部客户端
package strategy.examp02; import java.math.BigDecimal; //外部客户端 public class Client { public static void main(String[] args) { //1.创建老客户的报价策略,多态使用 IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy(); //2.创建报价上下文对象,并设置具体的报价策略 QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy); //3.调用报价上下文的方法 BigDecimal price = quoteContext.getPrice(new BigDecimal(100)); System.out.println("折扣价为:" +price); } }
控制台输出:
恭喜!老客户享有9折优惠!
折扣价为:90.00
这个时候,商城营销部推出了新的客户类型--MVP,可以享受7折优惠,那该怎么办?
这个很容易,只需要新增个报价策略的实现,然后外部客户端调用的时候,创建这个新增的报价策略实现,并放到策略上下文就可以啦,对原本的已经实现的代码没有任何的改动。
MVP用户的报价策略实现:
package strategy.examp02; import java.math.BigDecimal; //MVP客户的报价策略实现 public class MVPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("哇偶!MVP客户享受7折优惠!!!"); originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
外部客户端实现:
package strategy.examp02; import java.math.BigDecimal; //外部客户端 public class Client { public static void main(String[] args) { //创建MVP客户的报价策略 IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy(); //创建报价上下文对象,并设置具体的报价策略 QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy); //调用报价上下文的方法 BigDecimal price = quoteContext.getPrice(new BigDecimal(100)); System.out.println("折扣价为:" +price); } }
控制台输出:
哇偶!MVP客户享受7折优惠!!!
折扣价为:70.00
深入理解策略模式
策略模式的作用:
把具体的算法实现从业务逻辑中剥离出来,成一系列的独立算法类,使得可以相互替换。
策略模式的重点:
不是如何设计是实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。
策略模式就是把各个平等的具体实现进行抽象化,封装成独立的算法,然后经过上下文和具体的算法类进行交互。各个算法之间都是平等的,地位都是一致的,正是由于各个算法的平等性,所以是可替代的。但是同一时刻只能使用一个策略。
三国刘备取西川时,庞统给的上、中、下的三个计策:
上策:挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也。
中策:杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。
下策:退还白帝,连引荆州,慢慢进图益州,此为下计。
这三个计策都是取西川的计策,也就是攻取西川这个问题的具体的策略算法,刘备可以采用上策,可以采用中策,当然也可以采用下策,由此可见策略模式的各种具体的策略算法都是平等的,可以相互替换。
那谁来选择具体采用哪种计策(算法)?
在这个故事中当然是刘备选择了,也就是外部的客户端选择使用某个具体的算法,然后把该算法(计策)设置到上下文当中;
还有一种情况,客户端不选择具体的算法,把这个事情交给上下文,这相当于刘备说我不管哪种计策,只要攻下西川即可,具体怎么攻占有上下文决定。
//攻取西川的策略 2 public interface IOccupationStrategyWestOfSiChuan { 3 public void occupationWestOfSiChuan(String msg); 4 }
//攻取西川的上上计策 public class UpperStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { if (msg == null || msg.length() < 5) { //故意设置障碍,导致上上计策失败 System.out.println("由于计划泄露,上上计策失败!"); int i = 100/0; } System.out.println("挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!"); } }
//攻取西川的中计策 public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { System.out.println("杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。"); } }
//攻取西川的下计策 public class LowerStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { System.out.println("退还白帝,连引荆州,慢慢进图益州,此为下计。"); } }
//攻取西川参谋部,就是上下文啦,由上下文来选择具体的策略 2 public class OccupationContext { 3 4 public void occupationWestOfSichuan(String msg){ 5 //先用上上计策 6 IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy(); 7 try { 8 strategy.occupationWestOfSiChuan(msg); 9 } catch (Exception e) { 10 //上上计策有问题行不通之后,用中计策 11 strategy = new MiddleStrategy(); 12 strategy.occupationWestOfSiChuan(msg); 13 } 14 } 15 }
//此时外部客户端相当于刘备了,不管具体采用什么计策,只要结果(成功的攻下西川) public class Client { public static void main(String[] args) { OccupationContext context = new OccupationContext(); //这个给手下的人激励不够啊 context.occupationWestOfSichuan("拿下西川"); System.out.println("========================="); //这个人人有赏,让士兵有动力啊 context.occupationWestOfSichuan("拿下西川之后,人人有赏!"); } }
控制台输出
由于计划泄露,上上计策失败! 杨怀、高沛是蜀中名将,手下有精锐部队,而且据守关头,我们可以装作要回荆州,引他们轻骑来见,可就此将其擒杀,而后进兵成都,此为中计。 ========================= 挑选精兵,昼夜兼行直接偷袭成都,可以一举而定,此为上计计也!
策略模式的优点:
- 策略模式的功能通过抽象、封装来定义一系列的算法,是的算法之间可以相互替换,所以为这些算法定义一个公共的接口,用来约束算法的功能实现。如果算法有公共的功能,可以将接口变为抽象类,将公共功能放在了抽象父类里面。
- 策略模式的算法是可以相互替换,平等的,避免了if else 组织结构。
- 扩展性更好:更容易扩展策略,只需要新增一个策略实现类,然后在使用策略实现的地方,使用策略实现即可。
策略模式的缺点:
- 增加了对象的数量。
- 只适合扁平的算法结构。由于策略模式是平等的关系,实际上就是扁平的算法结构。
策略模式的本质:
分离算法,选择实现。
策略模式体现了开闭原则:策略模式将一系列的算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。
策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略都是兄弟关系,实现了同一个接口或者继承同一个抽象类。这样只需要实现策略的客户端保持面向抽象编程,就可以动态切换不同的策略实现进行替换。
以上就是策略模式的基本内容和使用,也是设计模式中另一个较为重要的模式,希望对大家有所帮助!!!