策略模式介绍
策略模式是一种行为模式,也是替代大量ifelse
的利器。它所能帮你解决的是场景,一般是具有同类可替代的行为逻辑算法场景。比如:不同类型的交易方式(信用卡、支付宝、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)等,都可以使用策略模式进行行为包装,供给外部使用。
我们这里的案例是通过使用购买商品时的几种不同折扣策略进行包装
例如:满减,折扣,n元购,直减等
使用面向过程的方式实现
- 代码如下:
根据类型不断扩展入参以及ifelse
代码可维护性会非常的差
/**
* 优惠券类型;
* 1. 直减券
* 2. 满减券
* 3. 折扣券
* 4. n元购
*/
public class CouponDiscountService {
public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
// 1. 直减券
if (1 == type) {
return skuPrice - typeContent;
}
// 2. 满减券
if (2 == type) {
if (skuPrice < typeExt) return skuPrice;
return skuPrice - typeContent;
}
// 3. 折扣券
if (3 == type) {
return skuPrice * typeContent;
}
// 4. n元购
if (4 == type) {
return typeContent;
}
return 0D;
}
}
使用策略模式优化
工程结构
结构并不算太复杂,包括一个接口类以及四种优惠策略的实现,以及最后提供了策略模式的上下控制类处理,整体的策略服务context类
itstack-demo-design-20-02
└── src
└── main
└── java
└── org.itstack.demo.design
├── event
│ └── MJCouponDiscount.java
│ └── NYGCouponDiscount.java
│ └── ZJCouponDiscount.java
│ └── ZKCouponDiscount.java
├── Context.java
└── ICouponDiscount.java
代码实现
优惠接口
- 定义了优惠卷折扣接口,增加了泛型参数用来提供不同类型的卷折扣信息
public interface ICouponDiscount<T> {
/**
* 优惠券金额计算
* @param couponInfo 券折扣信息;直减、满减、折扣、N元购
* @param skuPrice sku金额
* @return 优惠后金额
*/
BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
各种优惠策略的实现
满减
public class MJCouponDiscount implements ICouponDiscount<Map<String,String>> {
/**
* 满减计算
* 1. 判断满足x元后-n元,否则不减
* 2. 最低支付金额1元
*/
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;
}
}
直减
public class ZJCouponDiscount implements ICouponDiscount<Double> {
/**
* 直减计算
* 1. 使用商品价格减去优惠价格
* 2. 最低支付金额1元
*/
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;
}
}
折扣
public class ZKCouponDiscount implements ICouponDiscount<Double> {
/**
* 折扣计算
* 1. 使用商品价格乘以折扣比例,为最后支付金额
* 2. 保留两位小数
* 3. 最低支付金额1元
*/
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;
}
}
N元购
public class NYGCouponDiscount implements ICouponDiscount<Double> {
/**
* n元购购买
* 1. 无论原价多少钱都固定金额购买
*/
public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
return new BigDecimal(couponInfo);
}
}
- 以上是四种不同类型的优惠券计算折扣金额的策略方式,可以从代码中看到每一种优惠方式的优惠金额。每一种优惠方式根据泛型T获得不同的入参,从而实现不同的折扣算法
策略控制类
- 实现context用于提供对外的接口服务,并控制相应的策略作为底层支持
注意:这里也可以包装成map类型,以相应的服务为value,对应的泛型类型为key,这样就可以根据相应的泛型类型进行获取相应的服务了
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);
}
}
编写测试类(直减优惠)
@Test
public void test_zj() {
// 直减;100-10,商品100元
Context<Double> context = new Context<Double>(new ZJCouponDiscount());
BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
logger.info("测试结果:直减优惠后金额 {}", discountAmount);
}
测试结果
15:43:22.035 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:直减优惠后金额 90
Process finished with exit code 0
编写测试类(满减优惠)
@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));
logger.info("测试结果:满减优惠后金额 {}", discountAmount);
}
测试结果
15:43:42.695 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:满减优惠后金额 90
Process finished with exit code 0
策略模式与工厂模式的区别:
一句话概括:工厂模式中的工厂类负责提供实例对象,具体实例对象如何调用方法不管;策略模式是context控制器调用已封装好的各个策略(方法),而无法获得策略接口的实现类对象
换句话说:工厂模式类似英语考试的完形填空题(自己考虑填什么词、句子),策略模式类似信息匹配题(提前写好选择项,当你有多个填空时,直接选就好了,没有适合的就再多写几个选择项)。