策略模式
# 定义
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
策略模式是一种行为模式,也是替代大量 if...else 的利器。它所能帮你解决的是场景,一般是具有同类可替代的行为逻辑算法场景。比如;不同类型的交易方式(信用卡、支付宝、微信)、生成唯一 ID 策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)等,都可以使用策略模式进行行为包装,供给外部使用。
优点:
1. 算法可以自由切换。
2. 避免使用多重条件判断。
3. 扩展性良好。
缺点:
1. 策略类会增多。
2. 所有策略类都需要对外暴露。
使用场景:
1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2. 一个系统需要动态地在几种算法中选择一种。
3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
# 实践
本次模拟在购买商品时候使用的各种类型优惠券(满减、直减、折扣、n元购)
>这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠!!!
>
>这样的场景有时候用户用起来还是蛮爽的,但是最初这样功能的设定以及产品的不断迭代,对于程序员👨💻开发还是不太容易的。因为这里包括了很多的规则和优惠逻辑,所以我们模拟其中的一个计算优惠的方式,使用策略模式来实现。
代码结构:

策略模式模型结构:

- 整体的结构模式并不复杂,主要体现的不同类型的优惠券在计算优惠券方式的不同计算策略。
- 这里包括一个接口类(ICouponDiscount)以及四种优惠券类型的实现方式。
- 最后提供了策略模式的上下控制类处理,整体的策略服务。
优惠券接口:
```java
public interface ICouponDiscount<T> {
/**
* 优惠券金额计算
* @param couponInfo 券折扣信息;直减、满减、折扣、N元购
* @param skuPrice sku金额
* @return 优惠后金额
*/
BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
```
优惠券接口实现
1.满减
```java
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>> {
/**
* 满减计算
* 1. 判断满足x元后-n元,否则不减
* 2. 最低支付金额1元
*/
@Override
public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {
String x = couponInfo.get("x");
String o = couponInfo.get("n");
// 小于商品金额条件的,直接返回商品原价
if (skuPrice.compareTo(new BigDecimal(x)) < 0) {
return skuPrice;
}
// 减去优惠金额判断
BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));
if (discountAmount.compareTo(BigDecimal.ZERO) < 1) {
return BigDecimal.ONE;
}
return discountAmount;
}
}
```
2.直减
```java
public class ZJCouponDiscount implements ICouponDiscount<Double> {
/**
* 直减计算
* 1. 使用商品价格减去优惠价格
* 2. 最低支付金额1元
*/
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
if (discountAmount.compareTo(BigDecimal.ZERO) < 1) {
return BigDecimal.ONE;
}
return discountAmount;
}
}
```
3.折扣
```java
public class ZKCouponDiscount implements ICouponDiscount<Double> {
/**
* 折扣计算
* 1. 使用商品价格乘以折扣比例,为最后支付金额
* 2. 保留两位小数
* 3. 最低支付金额1元
*/
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
if (discountAmount.compareTo(BigDecimal.ZERO) < 1) {
return BigDecimal.ONE;
}
return discountAmount;
}
}
```
4.N元购
```java
public class NYGCouponDiscount implements ICouponDiscount<Double> {
/**
* n元购购买
* 1. 无论原价多少钱都固定金额购买
*/
@Override
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
return new BigDecimal(couponInfo);
}
}
```
策略控制类
```java
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);
}
}
```
- 策略模式的控制类主要是外部可以传递不同的策略实现,在通过统一的方法执行优惠策略计算。
- 另外这里也可以包装成 map 结构,让外部只需要对应的泛型类型即可使用相应的服务。
测试类
```java
@Slf4j
public class ApiTest {
@Test
public void test_zj() {
// 直减;100-10,商品100元
Context<Double> context = new Context<Double>(new ZJCouponDiscount());
BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
log.info("测试结果:直减优惠后金额 {}", discountAmount);
}
@Test
public void test_mj() {
// 满100减10,商品100元
Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount());
Map<String,String> mapReq = new HashMap<String, String>();
mapReq.put("x","100");
mapReq.put("n","10");
BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100));
log.info("测试结果:满减优惠后金额 {}", discountAmount);
}
@Test
public void test_zk() {
// 折扣9折,商品100元
Context<Double> context = new Context<Double>(new ZKCouponDiscount());
BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
log.info("测试结果:折扣9折后金额 {}", discountAmount);
}
@Test
public void test_nyg() {
// n元购;100-10,商品100元
Context<Double> context = new Context<Double>(new NYGCouponDiscount());
BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100));
log.info("测试结果:n元购优惠后金额 {}", discountAmount);
}
}
```
测试结果
```
14:56:16.502 [main] INFO ApiTest - 测试结果:直减优惠后金额 90
Process finished with exit code 0
14:56:28.371 [main] INFO ApiTest - 测试结果:满减优惠后金额 90
Process finished with exit code 0
14:56:39.431 [main] INFO ApiTest - 测试结果:折扣9折后金额 90.00
Process finished with exit code 0
14:56:48.849 [main] INFO ApiTest - 测试结果:n元购优惠后金额 90
Process finished with exit code 0
```
# 总结
- 以上的策略模式案例相对来说不并不复杂,主要的逻辑都是体现在关于不同种类优惠券的计算折扣策略上。结构相对来说也比较简单,在实际的开发中这样的设计模式也是非常常用的。另外这样的设计与命令模式、适配器模式结构相似,但是思路是有差异的。
- 通过策略设计模式的使用可以把我们方法中的 if 语句优化掉,大量的 if 语句使用会让代码难以扩展,也不好维护,同时在后期遇到各种问题也很难维护。在使用这样的设计模式后可以很好的满足隔离性与和扩展性,对于不断新增的需求也非常方便承接。
- 策略模式、适配器模式、组合模式等,在一些结构上是比较相似的,但是每一个模式是有自己的逻辑特点,在使用的过程中最佳的方式是经过较多的实践来吸取经验,为后续的研发设计提供更好的技术输出。
>原文链接:https://github.com/fuzhengwei/CodeGuide/blob/master/docs/md/develop/design-pattern/2020-07-05-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E3%80%8B.md

【设计模式】策略模式