诸葛亮的锦囊妙计,每一个锦囊就是一个策略
策略模式
简要介绍
一个一个的策略,就是封装的一个又一个的算法,并且这些算法之间可以互换
客户端使用时,可以有选择的使用某个算法,算法和客户端解耦,我们可以用多个算法解决同一个问题
使用场景
- 一个系统需要动态的在几种算法中选择一种(说的就是 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
|
客户端代码无需改动,直接动用上下文和算法交互即可