设计模式之策略模式
文章目录
前言
在软件开发中,我们经常遇到需要根据不同条件执行不同算法或行为的情况。传统的if-else或switch-case语句虽然直观,但随着业务逻辑复杂度的增加,这种硬编码方式会导致代码臃肿、难以维护且违反开闭原则。策略模式(Strategy Pattern)作为一种行为型设计模式,优雅地解决了这一问题。它将算法族定义为一组可互换的策略类,使得算法可以独立于使用它的客户端变化。
与创建型的工厂模式不同,策略模式关注的是行为的封装和替换,而非对象的创建。工厂模式通过统一的接口创建不同类型的对象,隐藏了实例化逻辑;而策略模式则定义了算法家族,使它们可以互相替换,让算法的变化独立于使用算法的客户。简单来说,工厂模式解决"怎么来"的问题,策略模式解决"怎么做"的问题。两者常结合使用,工厂创建策略对象,策略对象执行具体行为,下面还是根据现实业务支付场景用代码解释策略模式的使用。
一、策略模式(Strategy Pattern)
以实际业务场景中支付业务为例子,如支付中有账户现金支付、会员卡支付、银联支付
1.定义支付方式接口
public interface IPayment<T> {
/**
* 支付方法
* 这里使用范型传参,方便方法参数扩展
* @param payInfo 支付对象
* @param payAmount 支付金额
* @return
*/
APIResult executePay(T payInfo, BigDecimal payAmount);
// 统一定义 PayInfoReq 对象的话,可能随着业务扩展,PayInfoReq中的传参字段越来越多
//但是在不同的支付方式中,如现金支付业务,可能只需要部分字段
// APIResult executePay(PayInfoReq payInfoReq);
}
2.账户现金支付实现
@Service("cashPayment")
public class CashPayment implements IPayment<CashDTO> {
@Override
public APIResult executePay(CashDTO payInfo, BigDecimal payAmount) {
//1.扣减账户余额
System.out.println("扣减更新账户余额");
//2.消费账单记录
System.out.println("消费账单记录");
return APIResult.success();
}
}
3.银联支付实现
@Service("unionPayment")
public class UnionPayment implements IPayment<UnionPayDTO> {
@Override
public APIResult executePay(UnionPayDTO payInfo, BigDecimal payAmount) {
//1.商户配置信息获取
System.out.println("商户配置信息获取");
//2.调用银联支付接口
System.out.println("调用银联支付接口");
//3.记录消费账单
System.out.println("记录消费账单");
return APIResult.success();
}
}
4.会员卡支付方式实现
@Service("vipCardPayment")
public class VipCardPayment implements IPayment<VipCardDTO> {
@Override
public APIResult executePay(VipCardDTO payInfo, BigDecimal payAmount) {
//1.扣钱会员卡余额
System.out.println("扣钱会员卡余额");
//2.记录消费账单记录
System.out.println("记录消费账单记录");
//3.会员积分计算
System.out.println("会员积分计算");
return APIResult.success();
}
}
上面三个支付方式实现IPayment接口
5.支付策略控制类
public class IPayContext<T> {
private IPayment<T> pay;
public IPayContext(IPayment<T> pay) {
this.pay = pay;
}
//支付算法族-不同类型的支付方法
public APIResult execPay(T payInfo, BigDecimal payAmount){
APIResult payResult = pay.executePay(payInfo, payAmount);
return payResult;
}
}
6.测试账户现金支付
@Test
public void execPayTest(){
IPayment<CashDTO> cashPayment = new CashPayment();
IPayContext payContext = new IPayContext(cashPayment);
CashDTO cashDTO = new CashDTO();
APIResult apiResult = payContext.execPay(cashDTO, new BigDecimal("100"));
System.out.println(apiResult);
}
//测试结果打印:
//扣减更新账户余额
//消费账单记录
在上面的步骤中实现了策略模式的基本使用。
二、策略模式在开发中常见写法
在项目开发中一般会将策略组封装成Map结构,并定义好枚举类型,通过枚举类型确定指定支付业务方法的调用
先定义枚举,确定支付类型
public class Constants {
public enum PayWayEnum {
CASH_PAY(1, "账户现金支付"),
UNION_PAY(2, "银联支付"),
VIPCARD_PAY(3, "会员卡支付");
private Integer payWay;
private String payWayDesc;
PayWayEnum(Integer payWay, String payWayDesc) {
this.payWay = payWay;
this.payWayDesc = payWayDesc;
}
public Integer getPayWay() {
return payWay;
}
public String getPayWayDesc() {
return payWayDesc;
}
}
}
1.方式一
这里如果新增一种支付类型,需要手动维护该map结构
public class IPayContextMap_1 {
//支付算法策略组
public static Map<Integer, IPayment> payStrtegyMap = new HashMap<>();
@Resource
private IPayment<CashDTO> cashPayment;
@Resource
private IPayment<UnionPayDTO> unionPayment;
@Resource
private IPayment<VipCardDTO> vipCardPayment;
/**
* 第一种常见做法 维护策略map
* 如果补充一个新的支付方式,需要维护该 map
*/
@PostConstruct
public void init(){
payStrtegyMap.put(Constants.PayWayEnum.CASH_PAY.getPayWay(), cashPayment);
payStrtegyMap.put(Constants.PayWayEnum.UNION_PAY.getPayWay(), unionPayment);
payStrtegyMap.put(Constants.PayWayEnum.VIPCARD_PAY.getPayWay(), vipCardPayment);
}
2.方式二
通过注解的方式,自动维护支付策略组map结构
2.1 定义注解
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PayStrategy {
Constants.PayWayEnum pay();
}
2.2 组装支付策略组map
public class IPayContextMap_2 {
//支付算法策略组
public static Map<Integer, IPayment> payStrtegyMap = new HashMap<>();
/**
* 实例化支付方式对象
*/
@Resource
public List<IPayment> payList;
/**
* 初始化支付方式策略数据
*/
@PostConstruct
public void initPayMap(){
payList.forEach(item ->{
PayStrategy annotation = AnnotationUtils.findAnnotation(item.getClass(), PayStrategy.class);
if (null != annotation){
payStrtegyMap.put(annotation.pay().getPayWay(),item);
}
});
}
}
2.3 在相应的支付方式补充支付策略注解
//补充 PayStrategy 注解来识别支付方式
@PayStrategy(pay = Constants.PayWayEnum.CASH_PAY)
@Service("cashPayment")
public class CashPayment implements IPayment<CashDTO> {
@Override
public APIResult executePay(CashDTO payInfo, BigDecimal payAmount) {
//1.扣减账户余额
System.out.println("扣减更新账户余额");
//2.消费账单记录
System.out.println("消费账单记录");
return APIResult.success();
}
}
3.方式三
在方式二的基础上 组装支付策略Map 的改进:通过 Spring 自动注入所有 IPayment 实现类(List<IPayment> payList)
//类标识了 @Component 在依赖注入时会被Spring自动实例化
@Component
public class IPayContextMap_3 {
//支付算法策略组
public static Map<Integer, IPayment> payStrtegyMap = new HashMap<>();
//通过构造器实例化 类 IPayContextMap_3
public IPayContextMap_3(List<IPayment> payList){
payList.forEach(item ->{
PayStrategy annotation = AnnotationUtils.findAnnotation(item.getClass(), PayStrategy.class);
if (null != annotation){
payStrtegyMap.put(annotation.pay().getPayWay(),item);
}
});
}
}
三、总结
1.根据上面的代码可以总结策略模式的好处有以下几点:
1.避免冗长的条件判断
替代大量的 if-else 或 switch-case 逻辑,通过多态调用不同策略的实现。
示例: 支付方式选择(支付宝、微信、银行卡)不再需要硬编码条件分支。
2.开闭原则(OCP)
新增策略时无需修改原有代码,只需添加新实现类。
示例: 新增一个加密货币支付策略,只需实现 PaymentStrategy 接口,不影响其他代码。
3.代码复用与解耦
策略逻辑集中在独立类中,可被多个上下文复用。 上下文(Context)仅依赖抽象策略接口,与具体实现解耦。
4.运行时动态切换行为
通过 setStrategy() 方法灵活切换策略(如根据用户输入或配置)。 上面的代码中切换策略是提前定义好枚举类型 ,通过 Map结构来切换不同的策略方式
5.单元测试更简单
每个策略可独立测试,Mock 策略更容易。
2.策略模式和工厂模式的区别总结:
| 维度 | 策略模式 | 工厂模式 |
|---|---|---|
| 目的 | 封装算法或行为,实现灵活替换 | 封装对象创建过程,隐藏实例化细节 |
| 核心角色 | 策略接口 + 具体策略 + 上下文 | 工厂接口 + 具体工厂 + 产品 |
| 关注点 | 行为(怎么做) | 创建(谁来做) |
| 运行时交互 | 上下文可动态切换策略 | 工厂通常返回对象后不再干预 |
| 典型使用场景 | 支付方式、排序算法、折扣策略 | 数据库连接、日志记录器、线程池创建 |
| 组合使用 | 策略对象可能由工厂创建(如 Spring 中),上面支付方式便是这种方式:spring 通过IOC 创建支付策略对象 | 工厂返回的策略对象可被上下文使用 |
更多推荐


所有评论(0)