诸葛亮的锦囊妙计,每一个锦囊就是一个策略
策略模式
简要介绍
一个一个的策略,就是封装的一个又一个的算法,并且这些算法之间可以互换
客户端使用时,可以有选择的使用某个算法,算法和客户端解耦,我们可以用多个算法解决同一个问题
使用场景
- 一个系统需要动态的在几种算法中选择一种(说的就是 if else 里面逻辑一大坨的情况)
 
类图

例子
场景描述:商品优惠有多种算法,满减、直减、折扣和 N 元购等等
正常情况
我们判断商品价格,然后判断是那种优惠策略,然后再根据优惠算法继续具体计算优惠后的钱
就是写出了以下的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | public class OriginalDemo {     
 
 
 
 
 
 
      public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {                  if (type == 1) {             return skuPrice - typeContent;         }                  if (type == 2) {                          if (skuPrice < typeExt) {                 return skuPrice;             }             return skuPrice - typeContent;         }                  if (type == 3) {             return skuPrice * typeContent;         }                  if (type == 4) {             return typeContent;         }                  return skuPrice;     }
  }
  | 
我们上述写法虽然也能完成功能,但是还有缺点,「扩展性不能满足」比如满减,前三个参数并不能满足这个算法,所以只能添加一个 typeExt 参数,表示满减金额
如果再出现五花八门的算法呢,这个方法还不知道加多少参数,而且违背了我们的开闭原则,重复改这个方法,极易出错
使用策略模式
我们先定义一个顶层接口
1 2 3 4 5 6 7 8 9 10 11 12
   | public interface ICouponDiscount<T> {
 
      
 
 
 
 
 
       BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice); }
  | 
这样后续有啥算法直接实现这个接口,实现其中的方法就行
下面是四种优惠算法,四个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | public class MJCouponDiscount implements ICouponDiscount<Map<String, String>> {
      
 
 
 
 
 
      @Override     public BigDecimal discountAmount(Map<String, String> couponInfo, BigDecimal skuPrice) {         String x = couponInfo.get("x");         String n = couponInfo.get("n");
                   if (skuPrice.compareTo(new BigDecimal(x)) < 0) {             return skuPrice;         }                  BigDecimal result = skuPrice.subtract(new BigDecimal(n));                  if (result.compareTo(BigDecimal.ONE) < 0) {             return BigDecimal.ONE;         }         return result;     } }
  | 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public class NYGCouponDiscount implements ICouponDiscount<Double> {
      
 
 
 
 
      @Override     public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {         return new BigDecimal(couponInfo);     } }
  | 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | public class ZJCouponDiscount implements ICouponDiscount<Double> {
      
 
 
 
 
 
 
      @Override     public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {         BigDecimal result = skuPrice.subtract(new BigDecimal(couponInfo));                                                      if (result.compareTo(BigDecimal.ONE) < 1) {             return BigDecimal.ONE;         }         return result;     } }
  | 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class ZKCouponDiscount implements ICouponDiscount<Double> {     
 
 
 
 
 
 
 
      @Override     public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {         BigDecimal result = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);         if (result.compareTo(BigDecimal.ONE) < 1) {             return BigDecimal.ONE;         }         return result;     } }
  | 
集成所有算法的上下文类
1 2 3 4 5 6 7 8 9 10 11 12
   | public class Context<T> {
      private ICouponDiscount<T> couponDiscount;
      public Context(ICouponDiscount<T> couponDiscount) {         this.couponDiscount = couponDiscount;     }
      public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {         return couponDiscount.discountAmount(couponInfo, skuPrice);     } }
  | 
客户端使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | public class MainTest {
      public void zjTest() {                  Context<Double> context = new Context<>(new ZJCouponDiscount());         BigDecimal result = context.discountAmount(10D, new BigDecimal("10.5"));         System.out.println("直减策略,10.5 减 10:" + result);     }
      public void mjTest() {                  Context<Map<String, String>> context = new Context<>(new MJCouponDiscount());         HashMap<String, String> couponInfo = new HashMap<>();                  couponInfo.put("x", "10.5");         couponInfo.put("n", "10");         BigDecimal result = context.discountAmount(couponInfo, new BigDecimal("10.5"));         System.out.println("满减策略,满 10.5 减 10:" + result);     }
      public void zkTest() {                  Context<Double> context = new Context<>(new ZKCouponDiscount());         BigDecimal result = context.discountAmount(0.1D, new BigDecimal("10.5"));         System.out.println("折扣策略,10.5 打一折:" + result);     }
      public void nyTest() {         Context<Double> context = new Context<>(new NYGCouponDiscount());         BigDecimal result = context.discountAmount(0.5D, new BigDecimal("10.5"));         System.out.println("n元购策略:直接n元买下:" + result);     }
      public static void main(String[] args) {         MainTest mainTest = new MainTest();         mainTest.mjTest();         mainTest.zjTest();         mainTest.zkTest();         mainTest.nyTest();     } }
  | 
时序图
以例子中的 zjTest 方法为例,分析策略模式调用的时序图

为什么需要一个 context?我直接调接口不行吗?
可以参考上面的时序图,context 把客户端和算法接口解耦,让上下文去和算法接口打交道,而客户端只知道自己调了一个 context 的某个方法,就可以了,实际情况我们和算法接口打交道前后会有一些操作,这个时候直接 context 里面写就行,而不用客户端每次调用接口都要记得写
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public class Context<T> {
      private ICouponDiscount<T> couponDiscount;
      public Context(ICouponDiscount<T> couponDiscount) {         this.couponDiscount = couponDiscount;     }
      public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {         System.out.println("优惠金额计算中... 原价:" + skuPrice);         return couponDiscount.discountAmount(couponInfo, skuPrice);     } }
  | 
我们每次计算优惠金额的时候都要知道原价多少,再次执行 MainTest 类中的主方法:
1 2 3 4 5 6 7 8
   | 优惠金额计算中... 原价:10.5 满减策略,满 10.5 减 10:1 优惠金额计算中... 原价:10.5 直减策略,10.5 减 10:1 优惠金额计算中... 原价:10.5 折扣策略,10.5 打一折:1.05 优惠金额计算中... 原价:10.5 n元购策略:直接n元买下:0.5
   | 
客户端代码无需改动,直接动用上下文和算法交互即可