Java 设计模式——策略模式

HYF Lv3

策略模式(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
/**
* @author -侑枫
* @date 2024/6/6 23:16:11
*/
public interface PaymentMethodStrategy {
/**
* 支付策略接口
* @param amount 金额
* @param joinActivity 是否参加活动
*/
void payment(Double amount, Boolean joinActivity);

/**
* 优惠活动
* @param amount 金额
* @return 返现或者抽奖信息
*/
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
/**
* @author -侑枫
* @date 2024/6/6 23:18:25
*/
@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);
}

/**
* 随机抽奖金额系统
* @return 返回抽奖金额
*/
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
/**
* @author -侑枫
* @date 2024/6/6 23:21: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
/**
* @author -侑枫
* @date 2024/6/6 23:27:25
*/
@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
/**
* @author -侑枫
* @date 2024/6/6 23:36:25
*/
@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
/**
* @author -侑枫
* @date 2024/6/6 23:43:06
*/
@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);
// 判断折扣后价格,然后返回一个最低0.1 最高10元的价格
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
/**
* @author -侑枫
* @date 2024/6/6 23:36:25
*/
@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);
}

/**
* 根据支付类型执行相应的支付策略
* @param type 支付类型
* @param amount 支付金额
*/
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
/**
* <p>
* 服务实现类
* </p>
*
* @author -侑枫
* @since 2024/6/6 23:58:33
*/
@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);
}

}

在项目启动时,会自动加载所有支付策略
Bean 自动加载

使用 PostMan 发起请求得到结果:
请求结果

结语

总结一下,策略模式为我们提供了一种优雅的解决方案,用于处理需要根据不同情况选择不同算法或行为的问题。通过策略模式,我们可以将算法的实现与调用代码解耦,使得系统更加灵活、可维护和易于扩展。在许多场景下,策略模式仍然是一种非常有价值的设计模式,它可以帮助我们构建出更加灵活和可扩展的软件系统。

总结一下策略模式的优缺点:

遵循开闭原则:策略模式使得系统可以轻松地添加新的支付方式,而不需要修改现有代码。这样可以降低系统的耦合度,提高系统的可扩展性。

消除条件判断:通过策略模式,避免了大量的条件判断语句(if-elseswitch-case),使得代码更加清晰、简洁,易于理解和维护。

提高可维护性:每个支付策略被封装在一个单独的类中,使得代码结构更加清晰,易于理解和维护。当需要修改某种支付方式时,只需修改对应的策略类,而不影响其他代码。

灵活性强:策略模式允许客户端动态地选择和替换支付策略,从而适应不同的业务需求。客户端可以在运行时根据需要选择合适的策略,而不需要在代码中硬编码。

增加类的数量:每增加一种支付方式就需要创建一个新的策略类,可能会导致类的数量增加。虽然这提高了代码的可扩展性,但也增加了代码的复杂性和维护成本。

策略选择的复杂性:在某些情况下,需要额外的逻辑来选择合适的支付策略,尤其是在策略种类较多时。这可能会增加系统的复杂性和开发成本。


END

  • 标题: Java 设计模式——策略模式
  • 作者: HYF
  • 创建于 : 2024-06-07 01:19:32
  • 更新于 : 2024-07-27 21:21:50
  • 链接: https://youfeng.ink/StrategyPattern-14a28750960a/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。