设计模式之策略模式
来源自《重学Java设计模式》链接提取码:ytc3
策略模式
策略模式是一种行为模式,也是代替大量ifelse
的利器。它所能帮你解决的是场景,一般是具有同类可替代的行为逻辑算法场景。比如:不同类型的交易方式(信用卡、支付宝、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、leaf算法)等,都可以使用策略模式及进行行为包装、供给外部使用。
案例场景模拟
在本案例中我们模拟在购买商品时候使用的各种类型的优惠券(满减、直减、折扣)
在这个场景几乎也是大家的日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠。
这样的场景有时候用户用起来还是蛮爽的。但是最初这样的功能的设定以及产品的不断迭代,对于程序员开发还是不太容易的。因为这里包括了很多的规则和优惠逻辑,所以说我们模拟其中一个计算优惠的方式,使用策略模式来实现。
常规实现
这里我们先使用最粗暴的方式来实现功能
对于优惠券的设计最初可能非常简单,就是一个金额的抵扣,也没有现在这么多种类型。所以如果没有这样的场景的经验的话,往往设计上也是非常简单的。但随着产品功能的不断迭代,如果程序最初的设计不具备很好的扩展性,那么往后就会越来越混乱。
代码实现
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;
}
}
- 以上是不同类型的优惠券计算折扣后的实际金额。
- 入参包括:优惠券类型、优惠券金额、商品金额、因为有些优惠券是满多少减多少,所以增加了
typeExt
类型,这也是方法不好扩展的原因。 - 最后是整个方法体中对优惠券抵扣金额的实现,最开始可能是最简单的优惠券,后面随着产品功能的增加,不断的扩展
if
语句。实际的代码可能是要比这个多的多。
使用策略模式
策略模式结构模型
- 整体的结构模式并不复杂,主要体现在不同类型的优惠券在计算优惠券方式的不同计算器。
- 这里包括一个接口类(
ICouponDiscount
)以及四种不同优惠券类型的实现方式。 - 最后提供了策略模式的上下控制类处理,整体的策略服务。
代码实现
优惠券接口
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);
}
}
- 以上是四种不同类型的优惠券计算折扣的策略方式,可以从代码中看到每一种优惠方式的优惠金额。
策略控制类
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结构,让外部只需要对应的泛型类型即可使用相应的服务。
测试验证
直减优惠
@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);
}
result
12:58:07.344 [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);
}
result
12:59:16.266 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:满减优惠后金额 90
Process finished with exit code 0
折扣优惠
@Test
public void test_zk() {
// 折扣9折,商品100元
Context<Double> context = new Context<Double>(new ZKCouponDiscount());
BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
logger.info("测试结果:折扣9折后金额 {}", discountAmount);
}
result
13:00:02.267 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:折扣9折后金额 90.00
Process finished with exit code 0
n元购
@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));
logger.info("测试结果:n元购优惠后金额 {}", discountAmount);
}
result
13:00:36.726 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:n元购优惠后金额 90
Process finished with exit code 0
- 以上四组测试分别验证了不同类型优惠券的优惠策略,测试结果满足我们的预期。
- 这里四种优惠券最终都是在原价
100元
的基础上折扣10元
,最终支付90元
总结
- 以上的策略模式案例相对来说并不复杂,主要的逻辑都是体现在关于不同种类优惠券的计算折扣策略上。结构相对来说也比较简单,在实际开发中这样的设计模式也是非常常用的。另外这样的设计与命令模式。·、适配器模式结构类似,但是思路是有差异的。
- 通过策略模式的使用,可以把我们方法中的if语句优化掉,大量的if语句会让代码难以维护和扩展。在使用这样的设计模式后可以很好的满足隔离性和扩展性,对于不断新增的需求也非常方便承接。
评论区