【无标题】

设计模式之十四:策略模式详解及其在Spring与Java中的应用

引言

在软件开发中,我们经常会遇到这样的情况:实现同一个功能有多种算法或策略,而我们需要根据不同的情况选择不同的实现方式。传统的做法是将这些算法硬编码在业务逻辑中,通过大量的if-else或switch-case来判断使用哪种算法。这种做法不仅导致代码臃肿,而且难以维护和扩展。

策略模式(Strategy Pattern)正是为解决这类问题而生。本文将详细介绍策略模式的定义、结构、实现方式,并结合Java实际案例,探讨如何在Spring框架中优雅地应用策略模式。

一、策略模式概述

1.1 定义与核心思想

策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

核心思想:针对接口编程,而不是针对实现编程。将可变的部分从程序中抽象分离,形成算法的独立族,而在上下文(Context)中委派给具体的策略实现。

1.2 模式结构

策略模式包含三个核心角色:

  1. Context(上下文):持有一个策略对象的引用,负责调用策略对象的算法。
  2. Strategy(抽象策略):定义所有策略类必须实现的公共接口。
  3. 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 优点

  1. 开闭原则:可以在不修改原有代码的情况下,增加新的策略。
  2. 避免多重条件语句:消除大量的if-else或switch-case。
  3. 提高算法的复用性:策略可以被不同的上下文复用。
  4. 策略可以自由切换:由于策略类实现同一个接口,它们可以自由切换。

4.2 缺点

  1. 类数量增加:每个策略都是一个类,可能会产生很多策略类。
  2. 客户端必须了解策略:客户端需要知道有哪些策略可选,并选择合适的策略。
  3. 策略类之间无法共享数据:除非使用上下文传递数据。

五、使用总结与实践建议

5.1 适用场景

  • 系统中有多个类,它们之间的区别仅在于行为不同。
  • 需要动态选择算法或策略的场景。
  • 算法需要使用if-else或switch-case进行选择的场景。
  • 需要屏蔽算法实现细节的场景。

5.2 最佳实践

  1. 结合工厂模式:使用工厂模式创建策略对象,减少客户端对具体策略的依赖。
  2. 使用枚举定义策略类型:在策略工厂中使用枚举管理策略类型,提高代码可读性。
  3. Spring中利用依赖注入:充分利用Spring的IOC容器管理策略Bean,通过注解自动注册。
  4. 策略与状态模式的区别
    • 策略模式:客户端主动选择策略,策略之间相互独立。
    • 状态模式:状态随上下文变化自动切换,状态之间有关联。

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框架的支持下,策略模式的实现变得更加简单和高效。

相关推荐
闻哥1 小时前
Java虚拟机内存结构深度解析:从底层原理到实战调优
java·开发语言·jvm·python·面试·springboot
wangbing11251 小时前
平台介绍-SDK包
java
亓才孓2 小时前
[SpringIOC]NoUniqueBeanDefinitionException
java·spring
开开心心就好2 小时前
实用PDF批量加马赛克,抹除敏感信息绿色版
java·linux·开发语言·网络·人工智能·pdf·word2vec
kong79069282 小时前
SpringBoot Rest风格 API
java·spring boot·后端
Moshow郑锴2 小时前
WindowsRemoteAdmin(超级马里奥像素版)专为无远程登录桌面系统打造的便携式管理工具
java·powershell·远程控制·远程桌面·系统运维
骇城迷影2 小时前
代码随想录:栈和队列篇
java·服务器·算法
重生之后端学习2 小时前
124. 二叉树中的最大路径和
java·数据结构·算法·职场和发展·深度优先·图论
Renhao-Wan2 小时前
Java 算法实践(五):二叉树遍历与常见算法题
java·数据结构·算法