写点什么

如何优雅的使用策略模式

  • 2023-04-28
    湖南
  • 本文字数:4715 字

    阅读完需:约 15 分钟

最近这段时间,想给大家分享一下设计模式的一些用法以及在项目中怎么运用。


设计模式是软件设计中常见问题的典型解决方案。 它们就像能根据需求进行调整的预制蓝图, 可用于解决代码中反复出现的设计问题。


今天就拿其中一个问题来分析,使用策略模式来解决问题,没有了解过策略模式或者长时间不用已经忘了策略模式的小伙伴先来简单了解一下策略模式吧。

什么是策略模式?

策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个 if 判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化。


简单理解就是,针对不同的场景,使用不同的策略进行处理。

策略模式结构

  • Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。

  • Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。

策略模式适用场景

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们 的行为,那么使用策略模式可以动态地让一个对象在许多行 为中选择一种行为。

  • 一个系统需要动态地在几种算法中选择一种。

  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行 为就只好使用多重的条件选择语句来实现。

  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。


生活中比较常见的应用模式有:

  1. 电商网站支付方式,一般分为银联、微信、支付宝,可以采用策略模式。

  2. 电商网站活动方式,一般分为满减送、限时折扣、包邮活动,拼团等可以采用策略模式。

简单示例

场景:最近太热了,想要降降温,有什么办法呢

首先,定义一个降温策略的接口

public interface CoolingStrategy {
/** * 处理方式 */ void handle();
}
复制代码

定义 3 种降温策略;实现策略接口

public class IceCoolingStrategy implements CoolingStrategy {    @Override    public void handle() {        System.out.println("使用冰块降温");    }}
复制代码


public class FanCoolingStrategy implements CoolingStrategy {
@Override public void handle() { System.out.println("使用风扇降温"); }}
复制代码


public class AirConditionerCoolingStrategy implements CoolingStrategy {    @Override    public void handle() {        System.out.println("使用空调降温");    }}
复制代码

定义一个降温策略的上下文:

public class CoolingStrategyContext {
private final CoolingStrategy strategy;
public CoolingStrategyContext(CoolingStrategy strategy) { this.strategy = strategy; }
public void coolingHandle() { strategy.handle(); }
}
复制代码

测试

public class Main {    public static void main(String[] args) {                CoolingStrategyContext context = new CoolingStrategyContext(new FanCoolingStrategy());        context.coolingHandle();
context = new CoolingStrategyContext(new AirConditionerCoolingStrategy()); context.coolingHandle();
context = new CoolingStrategyContext(new IceCoolingStrategy()); context.coolingHandle(); }}
复制代码

运行结果:

使用风扇降温 使用空调降温 使用冰块降温 
复制代码

以上就是一个策略模式的简单实现

项目实战

场景

模拟在购买商品时候使用的各种类型优惠券(满减、直减、折扣、n 元购)


这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠!

用一坨坨代码实现

/** * 优惠券折扣计算接口 * <p> * 优惠券类型; * 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; }
}
复制代码
  • 以上是不同类型的优惠券计算折扣后的实际金额。

  • 入参包括;优惠券类型、优惠券金额、商品金额,因为有些优惠券是满多少减少多少,所以增加了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 结构,让外部只需要对应的泛型类型即可使用相应的服务。

测试类

public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
@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); }
@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); }

@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); }
@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); }
}
复制代码
  • 以上四组测试分别验证了不同类型优惠券的优惠策略,测试结果是满足我们的预期。

  • 这里四种优惠券最终都是在原价100元上折扣10元,最终支付90元

总结

通过策略设计模式的使用可以把我们方法中的 if 语句优化掉,大量的 if 语句使用会让代码难以扩展,也不好维护,同时在后期遇到各种问题也很难维护。在使用这样的设计模式后可以很好的满足隔离性与和扩展性,对于不断新增的需求也非常方便承接。


了解策略模式的优点和缺点,合理的使用策略模式,会让你的代码更加的整洁优雅


作者:初念初恋

链接:https://juejin.cn/post/7204293342948360250

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
如何优雅的使用策略模式_做梦都在改BUG_InfoQ写作社区