前言

在软件开发中,我们经常遇到需要根据不同条件执行不同算法或行为的情况。传统的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 创建支付策略对象工厂返回的策略对象可被上下文使用
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐