设计模式之十四:策略模式详解及其在Spring与Java中的应用
引言
在软件开发中,我们经常会遇到这样的情况:实现同一个功能有多种算法或策略,而我们需要根据不同的情况选择不同的实现方式。传统的做法是将这些算法硬编码在业务逻辑中,通过大量的if-else或switch-case来判断使用哪种算法。这种做法不仅导致代码臃肿,而且难以维护和扩展。
策略模式(Strategy Pattern)正是为解决这类问题而生。本文将详细介绍策略模式的定义、结构、实现方式,并结合Java实际案例,探讨如何在Spring框架中优雅地应用策略模式。
一、策略模式概述
1.1 定义与核心思想
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
核心思想:针对接口编程,而不是针对实现编程。将可变的部分从程序中抽象分离,形成算法的独立族,而在上下文(Context)中委派给具体的策略实现。
1.2 模式结构
策略模式包含三个核心角色:
- Context(上下文):持有一个策略对象的引用,负责调用策略对象的算法。
- Strategy(抽象策略):定义所有策略类必须实现的公共接口。
- ConcreteStrategy(具体策略):实现抽象策略接口,提供具体的算法实现。
1.3 类图
+---------------+ +-------------------+
| Context | | <<interface>> |
+---------------+ | Strategy |
| - strategy |------>+-------------------+
+---------------+ | + algorithm() |
| + execute() | +-------------------+
+---------------+ ▲
|
+---------------+---------------+
| | |
+-----------+----+ +-----------+----+ +-----------+----+
|ConcreteStrategyA| |ConcreteStrategyB| |ConcreteStrategyC|
+----------------+ +----------------+ +----------------+
| + algorithm() | | + algorithm() | | + algorithm() |
+----------------+ +----------------+ +----------------+
二、策略模式的Java实现示例
让我们通过一个电商促销活动的例子来演示策略模式的实现。
场景描述
一个电商平台需要支持多种促销策略:
- 满减促销
- 折扣促销
- 无促销(原价)
2.1 定义抽象策略接口
java
/**
* 促销策略接口
*/
public interface PromotionStrategy {
/**
* 计算促销后的价格
* @param originalPrice 原价
* @return 促销后价格
*/
double calculatePrice(double originalPrice);
/**
* 获取促销名称
* @return 促销名称
*/
String getPromotionName();
}
2.2 实现具体策略类
满减策略:满100减20
java
public class FullReductionStrategy implements PromotionStrategy {
private static final double FULL_AMOUNT = 100;
private static final double REDUCTION_AMOUNT = 20;
@Override
public double calculatePrice(double originalPrice) {
if (originalPrice >= FULL_AMOUNT) {
return originalPrice - REDUCTION_AMOUNT;
}
return originalPrice;
}
@Override
public String getPromotionName() {
return "满减促销";
}
}
折扣策略:8折优惠
java
public class DiscountStrategy implements PromotionStrategy {
private static final double DISCOUNT_RATE = 0.8;
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * DISCOUNT_RATE;
}
@Override
public String getPromotionName() {
return "折扣促销";
}
}
无促销策略:原价
java
public class NoPromotionStrategy implements PromotionStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice;
}
@Override
public String getPromotionName() {
return "无促销";
}
}
2.3 创建上下文类
java
/**
* 购物车 - 上下文角色
*/
public class ShoppingCart {
private PromotionStrategy promotionStrategy;
private List<Double> items = new ArrayList<>();
public ShoppingCart() {
this.promotionStrategy = new NoPromotionStrategy(); // 默认无促销
}
public void setPromotionStrategy(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void addItem(double price) {
items.add(price);
}
public double calculateTotal() {
double total = items.stream().mapToDouble(Double::doubleValue).sum();
return promotionStrategy.calculatePrice(total);
}
public void checkout() {
double finalPrice = calculateTotal();
System.out.println("使用" + promotionStrategy.getPromotionName() + ",应付金额:" + finalPrice);
}
}
2.4 客户端使用示例
java
public class Client {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem(80);
cart.addItem(30);
// 使用满减促销
cart.setPromotionStrategy(new FullReductionStrategy());
cart.checkout();
// 使用折扣促销
cart.setPromotionStrategy(new DiscountStrategy());
cart.checkout();
// 无促销
cart.setPromotionStrategy(new NoPromotionStrategy());
cart.checkout();
}
}
输出结果:
使用满减促销,应付金额:90.0
使用折扣促销,应付金额:88.0
使用无促销,应付金额:110.0
三、策略模式在Spring框架中的应用
Spring框架中大量使用了策略模式,下面介绍几个典型的应用场景。
3.1 ResourceLoader - 资源加载策略
Spring的ResourceLoader接口使用了策略模式来加载不同类型的资源。
java
public interface ResourceLoader {
Resource getResource(String location);
}
// 不同协议的资源加载策略
// ClassPathResource
// FileSystemResource
// UrlResource
3.2 InstantiationStrategy - Bean实例化策略
Spring在创建Bean实例时,使用了不同的实例化策略。
java
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner);
// ... 其他方法
}
// 具体策略实现
// SimpleInstantiationStrategy - 简单实例化
// CglibSubclassingInstantiationStrategy - CGLIB动态代理实例化
3.3 在Spring Boot中自定义策略模式的应用
让我们看一个在Spring Boot项目中优雅实现策略模式的例子。
场景:用户支付方式选择
java
// 1. 定义策略接口
public interface PaymentStrategy {
String getPaymentType();
PayResult pay(PayRequest request);
}
// 2. 定义策略注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PaymentType {
String value();
}
// 3. 实现具体策略
@Component
@PaymentType("alipay")
public class AlipayStrategy implements PaymentStrategy {
@Override
public String getPaymentType() {
return "alipay";
}
@Override
public PayResult pay(PayRequest request) {
System.out.println("使用支付宝支付:" + request.getAmount());
// 支付宝支付逻辑
return new PayResult(true, "支付宝支付成功");
}
}
@Component
@PaymentType("wechat")
public class WechatPayStrategy implements PaymentStrategy {
@Override
public String getPaymentType() {
return "wechat";
}
@Override
public PayResult pay(PayRequest request) {
System.out.println("使用微信支付:" + request.getAmount());
// 微信支付逻辑
return new PayResult(true, "微信支付成功");
}
}
@Component
@PaymentType("card")
public class CardPayStrategy implements PaymentStrategy {
@Override
public String getPaymentType() {
return "card";
}
@Override
public PayResult pay(PayRequest request) {
System.out.println("使用银行卡支付:" + request.getAmount());
// 银行卡支付逻辑
return new PayResult(true, "银行卡支付成功");
}
}
// 4. 策略工厂 - 注入所有策略
@Component
public class PaymentStrategyFactory implements ApplicationContextAware {
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(PaymentType.class);
for (Object bean : beans.values()) {
PaymentStrategy strategy = (PaymentStrategy) bean;
PaymentType paymentType = strategy.getClass().getAnnotation(PaymentType.class);
strategyMap.put(paymentType.value(), strategy);
}
}
public PaymentStrategy getStrategy(String paymentType) {
PaymentStrategy strategy = strategyMap.get(paymentType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付类型:" + paymentType);
}
return strategy;
}
}
// 5. 服务类使用
@Service
public class PaymentService {
@Autowired
private PaymentStrategyFactory strategyFactory;
public PayResult processPayment(String paymentType, PayRequest request) {
PaymentStrategy strategy = strategyFactory.getStrategy(paymentType);
return strategy.pay(request);
}
}
四、策略模式的优缺点
4.1 优点
- 开闭原则:可以在不修改原有代码的情况下,增加新的策略。
- 避免多重条件语句:消除大量的if-else或switch-case。
- 提高算法的复用性:策略可以被不同的上下文复用。
- 策略可以自由切换:由于策略类实现同一个接口,它们可以自由切换。
4.2 缺点
- 类数量增加:每个策略都是一个类,可能会产生很多策略类。
- 客户端必须了解策略:客户端需要知道有哪些策略可选,并选择合适的策略。
- 策略类之间无法共享数据:除非使用上下文传递数据。
五、使用总结与实践建议
5.1 适用场景
- 系统中有多个类,它们之间的区别仅在于行为不同。
- 需要动态选择算法或策略的场景。
- 算法需要使用if-else或switch-case进行选择的场景。
- 需要屏蔽算法实现细节的场景。
5.2 最佳实践
- 结合工厂模式:使用工厂模式创建策略对象,减少客户端对具体策略的依赖。
- 使用枚举定义策略类型:在策略工厂中使用枚举管理策略类型,提高代码可读性。
- Spring中利用依赖注入:充分利用Spring的IOC容器管理策略Bean,通过注解自动注册。
- 策略与状态模式的区别 :
- 策略模式:客户端主动选择策略,策略之间相互独立。
- 状态模式:状态随上下文变化自动切换,状态之间有关联。
5.3 与Spring Boot的完美结合
在Spring Boot项目中,策略模式的实现可以更优雅:
java
// 使用枚举定义策略类型
public enum PaymentTypeEnum {
ALIPAY("alipay", "支付宝"),
WECHAT("wechat", "微信支付"),
CARD("card", "银行卡");
private String code;
private String desc;
PaymentTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
}
// 结合Optional处理空值
public PayResult processPayment(String paymentType, PayRequest request) {
return Optional.ofNullable(strategyFactory.getStrategy(paymentType))
.map(strategy -> strategy.pay(request))
.orElseThrow(() -> new IllegalArgumentException("不支持的支付类型"));
}
结语
策略模式是一种非常实用且常用的设计模式,它体现了"封装变化"的设计原则。在实际开发中,合理运用策略模式可以让代码更加优雅、易于维护和扩展。特别是在Spring框架的支持下,策略模式的实现变得更加简单和高效。