Java策略模式:从传统实现到Spring实战,一篇吃透
在Java开发中,设计模式是解决重复问题、提升代码可维护性的核心工具,而策略模式作为行为型设计模式的典型代表,在业务场景中应用广泛。它能有效解决“多重if-else/switch”代码冗余问题,实现算法的动态切换与解耦。本文将从概念入手,逐步拆解传统实现与Spring环境下的优化方案,结合行业经验说明适用场景,并盘点经典实现案例,助力大家在实际开发中灵活运用。
一、策略模式核心概念
策略模式(Strategy Pattern)定义了一系列算法,将每个算法封装成独立的类,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。其核心思想是“封装变化、面向接口编程”,通过分离算法的定义与使用,提升代码的扩展性和可维护性。
策略模式包含三个核心角色:
-
抽象策略(Strategy):定义算法的公共接口或抽象类,规定算法必须实现的方法。
-
具体策略(ConcreteStrategy):实现抽象策略接口,封装具体的算法逻辑。
-
上下文(Context):持有抽象策略的引用,负责策略的切换与调用,隔离客户端与策略的直接耦合。
核心优势:消除条件判断语句、算法可动态切换、符合开闭原则(新增策略无需修改原有代码)。
二、传统策略模式实现(非Spring环境)
我们以“支付场景”为例,模拟传统策略模式的实现。假设业务需求支持支付宝、微信支付、银行卡支付三种方式,通过策略模式替代多重if-else判断。
2.1 步骤1:定义抽象策略(支付接口)
/**
* 抽象支付策略接口
*/
public interface PaymentStrategy {
// 支付方法
void pay(double amount);
// 获取支付类型
String getPayType();
}
2.2 步骤2:实现具体策略(三种支付方式)
/**
* 支付宝支付(具体策略)
*/
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount + "元");
// 实际场景中会包含支付宝SDK调用、签名验证等逻辑
}
@Override
public String getPayType() {
return "ALIPAY";
}
}
/**
* 微信支付(具体策略)
*/
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付:" + amount + "元");
// 微信支付SDK调用、回调处理等逻辑
}
@Override
public String getPayType() {
return "WECHAT_PAY";
}
}
/**
* 银行卡支付(具体策略)
*/
public class BankCardPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用银行卡支付:" + amount + "元");
// 银行卡加密、银联接口调用等逻辑
}
@Override
public String getPayType() {
return "BANK_CARD";
}
}
2.3 步骤3:实现上下文(支付上下文)
/**
* 支付上下文(负责策略切换与调用,保存策略引用实现切换功能)
*/
public class PaymentContext {
// 保存抽象策略引用,为动态切换提供基础
private PaymentStrategy paymentStrategy;
// 构造器注入策略(初始化时指定默认策略)
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 动态切换策略:更新保存的策略引用,实现灵活切换
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
System.out.println("支付方式切换为:" + paymentStrategy.getPayType());
}
// 委托策略执行支付(依赖保存的策略引用)
public void executePay(double amount) {
if (paymentStrategy == null) {
throw new IllegalArgumentException("支付方式未选择,请先设置支付策略");
}
paymentStrategy.pay(amount);
}
// 可选:获取当前使用的策略,便于业务感知
public PaymentStrategy getCurrentStrategy() {
return this.paymentStrategy;
}
}
2.4 步骤4:客户端调用
/**
* 支付上下文(负责策略切换与调用,保存策略引用实现切换功能)
*/
public class PaymentContext {
// 保存抽象策略引用,为动态切换提供基础
private PaymentStrategy paymentStrategy;
// 构造器注入策略(初始化时指定默认策略)
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 动态切换策略:更新保存的策略引用,实现灵活切换
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
System.out.println("支付方式切换为:" + paymentStrategy.getPayType());
}
// 委托策略执行支付(依赖保存的策略引用)
public void executePay(double amount) {
if (paymentStrategy == null) {
throw new IllegalArgumentException("支付方式未选择,请先设置支付策略");
}
paymentStrategy.pay(amount);
}
// 可选:获取当前使用的策略,便于业务感知
public PaymentStrategy getCurrentStrategy() {
return this.paymentStrategy;
}
}
2.5 传统实现总结
传统实现完全遵循策略模式核心思想,通过在上下文类中保存策略引用,配合`setPaymentStrategy`方法实现了策略的动态切换,有效解耦了算法与客户端。但仍存在明显痛点:客户端需要手动创建所有具体策略对象,需感知所有策略类的存在;若策略较多,手动管理策略对象的成本较高,且新增策略时需修改客户端调用代码(不符合开闭原则的极致要求)。
2.6 补充疑问解答:为何传统模式不用Map保存所有策略?
有疑问“传统模式为何不直接用Map保存所有策略引用”,核心原因的两点:一是传统模式核心目标是“解耦算法与动态切换”,而非“批量管理策略”,上下文仅需持有当前活跃策略,通过`set`方法替换即可,无需预载所有策略;二是手动编码下,Map预载需硬编码注册所有策略(如`map.put("ALIPAY", new AlipayStrategy())`),反而加剧耦合,新增策略需修改注册逻辑,违背开闭原则,且会造成未使用策略的对象冗余创建。
若传统场景需批量管理策略,可叠加简单工厂模式用Map封装注册逻辑,但仍需手动维护,直到Spring容器提供自动扫描能力,Map管理策略的优势才真正发挥。在Spring环境下,我们可通过依赖注入、容器管理等特性优化上述问题。
三、Spring环境下的策略模式优化实现
Spring框架的IOC容器、依赖注入、注解驱动等特性,可实现策略的自动注册、动态获取,彻底解决传统实现中客户端与策略类耦合的问题。以下基于Spring Boot环境重构支付场景。
3.1 优化思路
1. 给具体策略类添加自定义注解,标记支付类型;
2. 通过Spring上下文,在启动时扫描所有策略类,将“支付类型-策略对象”存入Map,实现策略的自动注册;
3. 上下文通过支付类型从Map中获取对应策略,实现动态调用。
3.2 步骤1:定义自定义注解(标记策略类型)
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* 支付策略注解(标记具体策略的支付类型)
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 让Spring扫描到策略类
public @interface PayStrategy {
String value(); // 支付类型(如ALIPAY、WECHAT_PAY)
}
3.3 步骤2:重构具体策略类(添加注解)
/**
* 支付宝支付(Spring管理的具体策略)
*/
@PayStrategy("ALIPAY")
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Spring环境-支付宝支付:" + amount + "元");
}
@Override
public String getPayType() {
return "ALIPAY";
}
}
// 微信支付、银行卡支付类似,仅需添加对应注解
@PayStrategy("WECHAT_PAY")
public class WechatPayStrategy implements PaymentStrategy {
// 实现代码同上
}
@PayStrategy("BANK_CARD")
public class BankCardPayStrategy implements PaymentStrategy {
// 实现代码同上
}
3.4 步骤3:实现策略注册与上下文(核心优化)
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Spring支付上下文(自动注册策略、动态获取)
*/
@Component
public class SpringPaymentContext implements ApplicationContextAware {
// 存储“支付类型-策略对象”映射
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
/**
* Spring启动时,扫描所有带@PayStrategy注解的策略类,存入Map
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取所有PaymentStrategy接口的实现类
Map<String, PaymentStrategy> beansOfType = applicationContext.getBeansOfType(PaymentStrategy.class);
beansOfType.forEach((beanName, strategy) -> {
// 获取注解上的支付类型
PayStrategy annotation = strategy.getClass().getAnnotation(PayStrategy.class);
if (Objects.nonNull(annotation)) {
String payType = annotation.value();
strategyMap.put(payType, strategy);
}
});
}
/**
* 根据支付类型获取策略并执行支付
*/
public void executePay(String payType, double amount) {
PaymentStrategy strategy = strategyMap.get(payType);
if (Objects.isNull(strategy)) {
throw new IllegalArgumentException("不支持的支付方式:" + payType);
}
strategy.pay(amount);
}
}
3.5 步骤4:Spring环境下客户端调用(Service层示例)
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 注入Spring上下文
private final SpringPaymentContext paymentContext;
// 构造器注入(推荐)
public OrderService(SpringPaymentContext paymentContext) {
this.paymentContext = paymentContext;
}
// 处理订单支付
public void processPayment(String payType, double amount) {
// 业务逻辑(订单校验、金额计算等)
System.out.println("订单支付处理中...");
// 调用上下文执行支付(无需感知具体策略类)
paymentContext.executePay(payType, amount);
}
}
// 控制器调用示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayController {
private final OrderService orderService;
public PayController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/pay")
public String pay(@RequestParam String payType, @RequestParam double amount) {
orderService.processPayment(payType, amount);
return "支付成功";
}
}
3.6 Spring实现优化点总结
1. 策略类由Spring容器管理,无需客户端手动创建,降低耦合;
2. 通过注解+上下文扫描实现策略自动注册,新增策略仅需添加注解和实现类,完全符合开闭原则;
3. 基于Map获取策略,避免手动切换,适配接口请求等动态场景。
四、结合行业经验:什么时候使用策略模式?
策略模式并非万能,需结合业务场景合理使用,以下是实际开发中高频适用场景及判断标准:
4.1 场景1:多重条件判断(if-else/switch)场景
当代码中出现超过3个条件判断,且每个分支对应独立的业务逻辑(算法)时,适合用策略模式替代。例如:支付方式选择、优惠券类型计算、物流方式匹配等。
反例:简单的条件赋值(如根据类型返回不同字符串),无需使用策略模式,避免过度设计。
4.2 场景2:算法需要动态切换的场景
当业务中需要根据不同场景(如用户等级、配置参数)动态切换算法逻辑时,策略模式可实现灵活切换。例如:会员折扣计算(普通会员9折、VIP8折、SVIP7折)、报表导出格式切换(Excel/PDF/CSV)。
4.3 场景3:算法独立且易扩展的场景
当业务逻辑中存在多个独立的算法,且未来可能新增或修改算法时,策略模式可隔离算法变化。例如:风控规则校验(不同场景下启用不同的风控规则集)、搜索排序算法(按热度/时间/评分排序)。
4.4 行业实战经验
在电商、金融、物流等行业,策略模式应用极为广泛。例如:电商平台的促销活动计算(满减、折扣、满赠可拆分为不同策略)、银行的利率计算(活期、定期、理财对应不同利率策略)、物流系统的路径规划算法(不同快递公司对应不同路径策略)。
核心判断原则:是否存在“多个独立的算法”,且“算法可能变化或扩展”。
五、策略模式的经典实现
除了自定义实现,Java原生API及主流框架中也有诸多策略模式的经典应用,以下是常见案例:
5.1 Java原生API中的策略模式
-
Comparator接口:Java集合框架中的排序策略,Comparator作为抽象策略,用户可自定义具体策略(如按字段升序/降序排序),通过Collections.sort()方法(上下文)执行排序。
// 自定义排序策略(按字符串长度排序) Comparator<String> lengthComparator = (s1, s2) -> s1.length() - s2.length(); List<String> list = Arrays.asList("apple", "banana", "cherry"); // 上下文调用策略 Collections.sort(list, lengthComparator); -
ThreadPoolExecutor的拒绝策略:ThreadPoolExecutor提供了4种拒绝策略(AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy),均实现了RejectedExecutionHandler接口(抽象策略),用户可自定义拒绝策略,实现线程池满时的不同处理逻辑。
5.2 框架中的策略模式实现
-
Spring MVC的ViewResolver:ViewResolver作为视图解析策略接口,不同的视图解析器(InternalResourceViewResolver、FreeMarkerViewResolver等)作为具体策略,Spring MVC根据配置动态选择视图解析策略,返回对应视图。
-
Spring Security的AuthenticationProvider:AuthenticationProvider作为认证策略接口,不同的认证方式(用户名密码认证、OAuth2认证、LDAP认证)对应不同的具体策略,Spring Security根据请求动态选择认证策略。
-
MyBatis的Executor:MyBatis中的Executor作为执行器策略接口,提供SIMPLE、REUSE、BATCH三种具体策略,分别对应简单执行器、可重用执行器、批量执行器,用户可通过配置切换。
六、策略模式的优缺点
6.1 优点
-
解耦算法定义与使用,消除条件判断,代码更简洁易维护;
-
符合开闭原则,新增策略无需修改原有代码,扩展性强;
-
算法可动态切换,适配不同业务场景。
6.2 缺点
-
策略类数量会增多(每个算法对应一个类),可能导致类膨胀;
-
客户端需了解所有策略的区别(如支付类型),才能正确选择策略。
七、总结
策略模式的核心价值在于“封装变化、动态切换”,尤其适合多重条件判断、算法独立且易扩展的场景。传统实现需手动管理策略对象,而Spring环境下通过注解扫描、容器管理,可实现策略的自动注册与动态获取,进一步提升代码的灵活性和可维护性。
在实际开发中,需避免过度设计,只有当算法存在明确的扩展或切换需求时,再引入策略模式。同时,可结合工厂模式、单例模式等,优化策略的创建与管理,实现更优雅的代码结构。
更多推荐


所有评论(0)