文章目录
1. 什么是策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端
策略模式允许在运行时选择不同的算法行为,而不会影响到客户端代码
策略模式的主要角色:
- 策略接口(Strategy):定义所有支持的算法的公共操作,通常是一个抽象类或接口(抽象类和接口的区别可以参考我的另一篇博文:Java中接口和抽象类的区别(语法层面的区别、设计理念层面的区别)
- 具体策略(Concrete Strategy):实现策略接口的类,实现了具体的算法
- 上下文(Context):使用策略接口的对象,负责维护一个具体策略对象的引用,并通过这个引用来调用具体策略实现的方法
策略模式符合设计模式六大基本原则中的开闭原则和单一职责原则(至于什么是设计模式的六大原则,可以参考我的另一篇博文:设计模式的六大基本原则(开闭原则、单一职责原则、里氏替换原则、接口隔离原则、依赖倒置原则、迪米特法则))
2. 策略模式的应用场景
以下是一些典型的使用策略模式的场景:
- 排序算法:在需要对数据进行排序时,可以使用策略模式来定义不同的排序算法(如快速排序、冒泡排序、归并排序等),然后在运行时根据需要选择合适的排序策略
- 支付方式:在电子商务系统中,不同的支付方式(如信用卡、PayPal、比特币等)可以被视为不同的策略,用户可以根据自己的偏好选择支付策略
- 折扣计算:在销售系统中,不同的折扣策略(如固定折扣、百分比折扣、阶梯折扣等)可以封装为不同的策略类,根据不同的销售活动选择合适的折扣策略
- 数据压缩:在文件处理或数据传输中,可以使用不同的数据压缩算法(如ZIP、GZIP、LZMA等),每种算法都可以是一个策略
- 日志记录:在日志管理中,可以将不同的日志记录方式(如文件记录、数据库记录、远程记录等)实现为策略
- 错误处理:在异常处理中,不同的错误处理策略(如重试、忽略、记录日志等)可以作为策略实现
策略模式的应用场景通常具有以下特点:
- 多个类只区别在表现行为上,可以使用一个公共接口来定义这些行为
- 算法的使用环境(客户端)不应该包含算法逻辑,这些算法逻辑应该被封装起来
- 算法之间可以互换,客户端应该能够在运行时选择不同的算法
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现
3. 策略模式的优点和缺点
3.1 优点
- 算法可以自由切换:策略模式使得算法可以在不影响到客户端的情况下进行切换
- 避免使用多重条件判断:在策略模式中,我们可以避免使用多重if-else或switch-case语句,使得代码更加简洁
- 扩展性良好:策略模式具有良好的扩展性,当需要添加新的算法时,只需要实现策略接口即可
- 维护性好:策略模式将算法封装在独立的类中,使得它们更容易维护和理解
3.2 缺点
- 客户端需要知道所有的策略类:客户端需要知道有哪些策略类可用,才能选择合适的策略
- 增加了对象的数目:策略模式会创建多个策略类和对象,在一定程度上增加了系统的复杂性
4. 策略模式在项目中的应用(以支付方式为例)
本次演示的环境:JDK 17.0.7
+ SpringBoot 3.0.2
4.1 没有应用策略模式前的代码
以下代码的主要作用是根据支付类型调用对应的 Bean 的支付方法来完成支付功能
从业务代码的角度来说,这段代码是没有什么明显的问题的,但是如果以后支付的类型变得越来越多,switch 语句的分支也会越来越多,通过构造器传进来的 Bean 也会越来越多,不利于我们后期的维护
import cn.edu.scau.service.AliPayService;
import cn.edu.scau.service.UnionPayService;
import cn.edu.scau.service.WeChatPayService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
private final WeChatPayService wechatPayService;
private final AliPayService aliPayService;
private final UnionPayService unionPayService;
public PayController(WeChatPayService wechatPayService, AliPayService aliPayService, UnionPayService unionPayService) {
this.wechatPayService = wechatPayService;
this.aliPayService = aliPayService;
this.unionPayService = unionPayService;
}
@GetMapping("/pay")
public void pay(Integer payType, Long orderId) {
switch (payType) {
case 1:
wechatPayService.pay(orderId); // 微信支付
break;
case 2:
aliPayService.pay(orderId); // 支付宝支付
break;
case 3:
unionPayService.pay(orderId); // 银联支付
break;
default:
throw new RuntimeException("不支持的支付方式");
}
}
}
4.2 应用策略模式改造代码
4.2.1 定义一个抽象的支付方式接口
PayStrategy.java
public interface PayStrategy {
void pay(Long orderId);
}
4.2.2 让具体的支付方式实现抽象的支付方式接口
WeChatPayService.java
import org.springframework.stereotype.Service;
@Service
public class WeChatPayService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("微信支付");
}
}
AliPayService.java
import org.springframework.stereotype.Service;
@Service
public class AliPayService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("支付宝支付");
}
}
UnionPayService.java
import org.springframework.stereotype.Service;
@Service
public class UnionPayService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("银联支付");
}
}
4.2.3 根据支付类型获取实现了对应支付方式的 Bean
我们需要一个策略的上下文(也叫策略工厂)来生产出具体的策略实现
通过策略上下文的构造方法进行自动装配,在容器初始化的时候就将三种具体的支付方式放入一个 Map 中(Spring 3.0 出现的新特性,可以通过 List 来接收多个 Bean)
为什么要放到一个 Map 里面呢,因为我们希望能够根据支付类型迅速地获取到实现了该种支付方式的 Bean
但现在有一个问题,在初始化 Map 的时候,key 应该是什么类型呢,1、2、3、4 吗,好像也不太合适
我们可以维护一个支付类型的枚举类型,把支付方式和支付类型对应的数字维护起来
public enum PayEnum {
WECHAT_PAY(1),
ALI_PAY(2),
UNION_PAY(3);
private final Integer value;
PayEnum(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
public static PayEnum fromValue(Integer value) {
for (PayEnum payEnum : PayEnum.values()) {
if (payEnum.getValue().equals(value)) {
return payEnum;
}
}
throw new IllegalArgumentException("Invalid value: " + value);
}
}
4.2.4 在抽象接口中添加获取获取具体支付对象的枚举类型
import cn.edu.scau.context.PayEnum;
public interface PaymentStrategy {
void pay(Long orderId);
PayEnum getPayEnum();
}
4.2.5 让具体的支付方法返回对应的枚举类型
WeChatPaymentService.java
import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;
@Service
public class WeChatPaymentService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("微信支付");
}
@Override
public PayEnum getPayEnum() {
return PayEnum.WECHAT_PAY;
}
}
AliPaymentService.java
import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;
@Service
public class AliPaymentService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("支付宝支付");
}
@Override
public PayEnum getPayEnum() {
return PayEnum.ALI_PAY;
}
}
UnionPaymentService.java
import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;
@Service
public class UnionPaymentService implements PaymentStrategy {
@Override
public void pay(Long orderId) {
System.out.println("银联支付");
}
@Override
public PayEnum getPayEnum() {
return PayEnum.UNION_PAY;
}
}
4.2.6 添加枚举类型后的策略上下文
import cn.edu.scau.service.PaymentStrategy;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class PaymentStrategyContext {
public final ConcurrentHashMap<PayEnum, PaymentStrategy> handleStrategyMap = new ConcurrentHashMap<>();
public PaymentStrategyContext(List<PaymentStrategy> paymentStrategyList) {
paymentStrategyList.forEach(paymentStrategy -> handleStrategyMap.put(paymentStrategy.getPayEnum(), paymentStrategy));
}
}
我们还需要在策略上下文中添加一个根据支付类型获取对应支付对象的方法
import cn.edu.scau.service.PaymentStrategy;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class PaymentStrategyContext {
public final ConcurrentHashMap<PayEnum, PaymentStrategy> handleStrategyMap = new ConcurrentHashMap<>();
public PaymentStrategyContext(List<PaymentStrategy> paymentStrategyList) {
paymentStrategyList.forEach(paymentStrategy -> handleStrategyMap.put(paymentStrategy.getPayEnum(), paymentStrategy));
}
public PaymentStrategy getPaymentStrategy(PayEnum payEnum) {
return handleStrategyMap.get(payEnum);
}
}
4.3 改造后的代码
应用策略模式后,我们就不需要在 controller 层逐个注入支付方式对应的 Bean 了,只需要注入策略上下文,然后根据策略上下文获取到对应支付方式对应的 Bean
import cn.edu.scau.context.PayEnum;
import cn.edu.scau.context.PaymentStrategyContext;
import cn.edu.scau.service.PaymentStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
private final PaymentStrategyContext paymentStrategyContext;
public PayController(PaymentStrategyContext paymentStrategyContext) {
this.paymentStrategyContext = paymentStrategyContext;
}
@GetMapping("/pay")
public void pay(Integer payType, Long orderId) {
PaymentStrategy paymentStrategy = paymentStrategyContext.getPaymentStrategy(PayEnum.fromValue(payType));
paymentStrategy.pay(orderId);
}
}
4.4 改造前和改造后代码的对比
改造前的代码
改造后的代码
我们可以看到,改造后的代码,无论是从代码的可读性,还是从后期的维护性来说,都得到了提升
我们后续如果要添加一个新的支付方式的话,只需要再创建一个新支付方式对应的 Bean,接着添加一个新的枚举类型就可以了
标签:策略,pay,模式,PayEnum,应用,支付,import,public From: https://blog.csdn.net/m0_62128476/article/details/142322139