策略模式(Strategy Pattern)
是一种软件设计模式,属于行为型模式。它允许在运行时选择算法的行为,将不同的算法封装成独立的类,并且使它们可以相互替换,从而使得算法的选择和使用可以独立于客户端代码而变化。
策略模式简介
策略模式的核心思想是将算法封装成独立的类,使得算法的变化不会影响到客户端使用该算法的代码。它使得系统更加灵活、可扩展,避免出现大量 if-else
或者 switch-case
,同时也符合开闭原则(Open/Closed Principle)
,即对扩展开放,对修改关闭。
当然,它也有缺点,引入了多个具体策略类,可能会增加类的数量,在存在大量的算法时,可能会导致具体策略类的爆炸性增长,使得管理和维护变得困难。
所以,策略模式在可预见的、新增策略有限的情况下是非常适用的,比如多渠道支付。因为在这种情况下,策略类的数量是有限的,不会无限增加;比如一件商品根据 VIP 等级来计算只有有限的几种折扣价。在类似这些情况下,策略模式能够有效地帮助我们实现算法的使用和实现分离,同时也能够保持系统的灵活性和可扩展性。
如何使用策略模式
我们模拟一个多渠道支付实现,来学习如何使用策略模式,假设我们有以下五种支付模式:支付宝、微信、银联、信用卡、数字人民币。
假设目前处于 618 消费大促期间,诸神争霸,共有以下限时活动:
使用银联卡没有活动;
使用信用卡每单随机减 0.1~5 元;
使用微信消费每单立刻返现百分2%,最高 10 元,最低 0.1 元;
使用支付宝支付每满 100 元抽奖一次,奖励金额为 0.8, 8.8, 88.8 元;
数字人民币全国推广活动,每满 300 减 30 元
定义一个支付策略接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public interface PaymentMethodStrategy {
void payment(Double amount, Boolean joinActivity);
String limitedTimePromotion(Double amount); }
|
具体策略类实现
支付宝支付:
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 42 43 44 45 46 47 48 49 50 51 52
|
@Component public class AliPaymentStrategy implements PaymentMethodStrategy { @Override public void payment(Double amount, Boolean joinActivity) { if (!joinActivity) { System.out.printf("使用支付宝支付:%s元\n", amount); return; } BigDecimal amountBigDecimal = BigDecimal.valueOf(amount); BigDecimal threshold = BigDecimal.valueOf(100d); if (amountBigDecimal.compareTo(threshold) < 0) { System.out.printf("使用支付宝支付:%s元\n", amount); return; } String raffleInformation = limitedTimePromotion(amount); System.out.printf("使用支付宝支付:%s元,%s\n", amount, raffleInformation); }
@Override public String limitedTimePromotion(Double amount) { int numberOfDraws = (int) (amount / 100); StringBuilder sb = new StringBuilder(); for (int i = 0; i < numberOfDraws; i++) { double drawLotteryAmount = drawLottery(); sb.append(drawLotteryAmount); if (i < numberOfDraws - 1) { sb.append(", "); } } return String.format("抽奖%s次,中奖金额为:%s", numberOfDraws, sb); }
private static double drawLottery() { Random random = new Random(); int rand = random.nextInt(100); if (rand < 90) { return 0.88; } else if (rand < 99) { return 8.88; } else { return 88.88; } } }
|
信用卡支付:
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
|
@Component public class CreditCardPaymentStrategy implements PaymentMethodStrategy { @Override public void payment(Double amount, Boolean joinActivity) { if (!joinActivity) { System.out.printf("使用信用卡支付:%s元\n", amount); return; } String raffleInformation = limitedTimePromotion(amount); System.out.printf("使用信用卡支付:%s元,%s\n", amount, raffleInformation); }
@Override public String limitedTimePromotion(Double amount) { Random random = new Random(); double discountAmount = random.nextDouble() * 4.9 + 0.1; BigDecimal bd = new BigDecimal(discountAmount); bd = bd.setScale(2, RoundingMode.HALF_UP); return String.format("本次支付随机立减%s元", bd.doubleValue()); } }
|
数字人民币支付
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
|
@Component public class DigitalRmbPaymentStrategy implements PaymentMethodStrategy { @Override public void payment(Double amount, Boolean joinActivity) { if (!joinActivity) { System.out.printf("使用数字人民币支付:%s元\n", amount); return; } BigDecimal amountBigDecimal = BigDecimal.valueOf(amount); BigDecimal threshold = BigDecimal.valueOf(300d); if (amountBigDecimal.compareTo(threshold) < 0) { System.out.printf("使用数字人民币支付:%s元\n", amount); return; } String raffleInformation = limitedTimePromotion(amount); System.out.printf("使用数字人民币支付,%s\n", raffleInformation); }
@Override public String limitedTimePromotion(Double amount) { BigDecimal discount = BigDecimal.valueOf(30d); BigDecimal numberOfDiscounts = BigDecimal.valueOf(amount / 300d); BigDecimal totalDiscount = discount.multiply(numberOfDiscounts).setScale(2, RoundingMode.HALF_UP); BigDecimal discountedAmount = BigDecimal.valueOf(amount).subtract(totalDiscount); return String.format("原价%s,本次活动立减%s元,折后价格%s元", amount, totalDiscount, discountedAmount); } }
|
储蓄卡支付
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@Component public class UnionPayCardPaymentStrategy implements PaymentMethodStrategy { @Override public void payment(Double amount, Boolean joinActivity) { if (!joinActivity) { System.out.printf("使用银联卡支付:%s元\n", amount); return; } String raffleInformation = limitedTimePromotion(amount); System.out.printf("使用银联卡支付:%s元,%s\n", amount, raffleInformation); }
@Override public String limitedTimePromotion(Double amount) { return null; } }
|
微信支付:
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
|
@Component public class WechatPaymentStrategy implements PaymentMethodStrategy { @Override public void payment(Double amount, Boolean joinActivity) { if (!joinActivity) { System.out.printf("使用微信支付:%s元\n", amount); return; } String raffleInformation = limitedTimePromotion(amount); System.out.printf("使用微信支付:%s元,%s\n", amount, raffleInformation); }
@Override public String limitedTimePromotion(Double amount) { BigDecimal discountBigDecimal = BigDecimal.valueOf(0.2d); BigDecimal amountBigDecimal = BigDecimal.valueOf(amount); BigDecimal discountedPrice = discountBigDecimal.multiply(amountBigDecimal); BigDecimal lowerLimit = BigDecimal.valueOf(0.1); BigDecimal upperLimit = BigDecimal.valueOf(10.0); if (discountedPrice.compareTo(lowerLimit) < 0) { return String.format("返现金额为%s", lowerLimit); } if (discountedPrice.compareTo(upperLimit) > 0) { return String.format("返现金额为%s", upperLimit); } return String.format("返现金额为%s", discountedPrice); } }
|
** 可以看到,每个具体策略类,都实现了 PaymentMethodStrategy
接口,重写了各自的支付方式和活动方式。**
使用策略模式
支付策略工具类
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
|
@Component @AllArgsConstructor public class PaymentUtils { public Map<String, PaymentMethodStrategy> strategies; private final ApplicationContext context;
@PostConstruct public void init() { Map<String, PaymentMethodStrategy> beans = context.getBeansOfType(PaymentMethodStrategy.class); strategies.putAll(beans); }
public void pay(String type, double amount, Boolean joinActivity) { PaymentMethodStrategy strategy = strategies.get(type); if (strategy != null) { strategy.payment(amount, joinActivity); } else { throw new IllegalArgumentException("无效的支付类型"); } } }
|
接下来只需要在需要实现该算法的地方注入调用 pay
方法即可,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Service @RequiredArgsConstructor public class OrderServiceImpl extends ServiceImpl<OrderMapper,Order> implements IOrderService {
private final PaymentUtils paymentUtils;
@Override public void test () { paymentUtils.pay("aliPaymentStrategy", 599, true); paymentUtils.pay("creditCardPaymentStrategy", 599, true); paymentUtils.pay("digitalRmbPaymentStrategy", 599, true); paymentUtils.pay("unionPayCardPaymentStrategy", 599, false); paymentUtils.pay("wechatPaymentStrategy", 599, true); }
}
|
在项目启动时,会自动加载所有支付策略
使用 PostMan
发起请求得到结果:
结语
总结一下,策略模式为我们提供了一种优雅的解决方案,用于处理需要根据不同情况选择不同算法或行为的问题。通过策略模式,我们可以将算法的实现与调用代码解耦,使得系统更加灵活、可维护和易于扩展。在许多场景下,策略模式仍然是一种非常有价值的设计模式,它可以帮助我们构建出更加灵活和可扩展的软件系统。
总结一下策略模式的优缺点:
遵循开闭原则:策略模式使得系统可以轻松地添加新的支付方式,而不需要修改现有代码。这样可以降低系统的耦合度,提高系统的可扩展性。
消除条件判断:通过策略模式,避免了大量的条件判断语句(if-else
或 switch-case
),使得代码更加清晰、简洁,易于理解和维护。
提高可维护性:每个支付策略被封装在一个单独的类中,使得代码结构更加清晰,易于理解和维护。当需要修改某种支付方式时,只需修改对应的策略类,而不影响其他代码。
灵活性强:策略模式允许客户端动态地选择和替换支付策略,从而适应不同的业务需求。客户端可以在运行时根据需要选择合适的策略,而不需要在代码中硬编码。
增加类的数量:每增加一种支付方式就需要创建一个新的策略类,可能会导致类的数量增加。虽然这提高了代码的可扩展性,但也增加了代码的复杂性和维护成本。
策略选择的复杂性:在某些情况下,需要额外的逻辑来选择合适的支付策略,尤其是在策略种类较多时。这可能会增加系统的复杂性和开发成本。
END