Spring + 设计模式 (十三) 行为型 - 策略模式

策略模式

1. 引言

策略模式(Strategy Pattern)是一种行为型设计模式,旨在将一组可互换的算法或行为封装成独立的策略类,使得算法的变化独立于使用算法的客户端。其核心思想是用组合替代继承,用接口约定行为,用配置实现灵活切换。通过将不同的行为逻辑抽取为独立类,策略模式不仅提升了代码的可维护性和扩展性,还能有效避免条件分支的复杂化。策略模式特别适合处理那些需要在运行时动态选择行为或算法的场景。

2. 实际开发中的用途

策略模式在实际开发中广泛应用于需要动态切换行为或算法的场景。以下是一些常见的应用场景:

  • 表单校验:根据输入类型(如邮箱、手机号、密码)选择不同的校验规则。
  • 支付方式:支持多种支付渠道(如支付宝、微信、银联)并动态切换。
  • 促销折扣:针对不同用户(如普通用户、会员、VIP)应用不同的折扣策略。
  • 日志存储:根据配置选择日志存储方式(如文件、数据库、云存储)。
  • 序列化协议:支持多种序列化格式(如JSON、XML、Protobuf)。
  • 文件上传:根据文件类型或大小选择不同的上传策略(如本地存储、云存储)。

通过策略模式,开发者可以避免使用冗长的if/elseswitch语句,将每种行为封装为独立的类,从而实现逻辑与行为的彻底解耦。这不仅提高了代码的可读性和可测试性,还为后续扩展提供了便利。

3. 开发中的示例

以订单折扣为例,不同类型的用户(普通用户、会员用户、VIP用户)可能享受不同的折扣策略。通过策略模式,我们可以将每种折扣算法封装为独立的策略类,便于维护和扩展。

java 复制代码
// 折扣策略接口
public interface DiscountStrategy {
    BigDecimal calculate(BigDecimal originalPrice);
}

// 普通用户:无折扣
public class NormalDiscount implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice;
    }
}

// 会员用户:9折
public class MemberDiscount implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice.multiply(BigDecimal.valueOf(0.9));
    }
}

// VIP用户:8折
public class VipDiscount implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice.multiply(BigDecimal.valueOf(0.8));
    }
}

// 上下文类:管理策略选择
public class DiscountContext {
    private final DiscountStrategy strategy;

    public DiscountContext(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    public BigDecimal executeStrategy(BigDecimal originalPrice) {
        return strategy.calculate(originalPrice);
    }
}

使用示例

java 复制代码
public class Main {
    public static void main(String[] args) {
        BigDecimal price = BigDecimal.valueOf(100.00);

        // 普通用户
        DiscountContext context = new DiscountContext(new NormalDiscount());
        System.out.println("普通用户价格: " + context.executeStrategy(price)); // 输出: 100.00

        // 会员用户
        context = new DiscountContext(new MemberDiscount());
        System.out.println("会员用户价格: " + context.executeStrategy(price)); // 输出: 90.00

        // VIP用户
        context = new DiscountContext(new VipDiscount());
        System.out.println("VIP用户价格: " + context.executeStrategy(price)); // 输出: 80.00
    }
}

在这个例子中,客户端通过组合不同的DiscountStrategy实现,动态选择折扣逻辑。如果需要添加新的折扣类型(如"超级VIP"),只需实现DiscountStrategy接口并创建新类,无需修改现有代码,符合开闭原则

4. Spring源码中的应用

Spring框架大量使用了策略模式,典型案例是org.springframework.beans.factory.support.InstantiationStrategy接口。该接口定义了Bean实例化的策略,允许Spring根据不同场景(如构造函数注入、CGLIB代理)动态切换实例化逻辑。

java 复制代码
// org.springframework.beans.factory.support.InstantiationStrategy
public interface InstantiationStrategy {
    Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner)
        throws BeansException;
}

Spring提供了以下实现类:

  • SimpleInstantiationStrategy:通过反射创建Bean实例,适用于大多数场景。
  • CglibSubclassingInstantiationStrategy:通过CGLIB动态生成子类,用于支持AOP代理。

AbstractAutowireCapableBeanFactory中,Spring通过策略模式动态调用实例化逻辑:

java 复制代码
protected Object createBeanInstance(...) {
    Object beanInstance = this.instantiationStrategy.instantiate(...);
    ...
}

通过注入不同的InstantiationStrategy实现,Spring能够灵活支持多种对象创建方式。这种设计不仅体现了策略模式的"可插拔"特性,还与Spring的IoC容器机制无缝集成。

另一个例子是Spring Security中的AuthenticationProvider,它允许开发者定义多种认证策略(如用户名密码、OAuth、LDAP),并通过配置动态切换。

5. Spring Boot代码案例

以下是一个基于Spring Boot的策略模式示例,展示如何实现动态选择"登录方式"策略(如密码登录、短信验证码登录、第三方OAuth登录)。

java 复制代码
// 策略接口
public interface LoginStrategy {
    boolean login(String identifier, String credential);
}

// 密码登录策略
@Component("passwordLogin")
public class PasswordLoginStrategy implements LoginStrategy {
    @Override
    public boolean login(String identifier, String credential) {
        System.out.println("密码登录: 用户名=" + identifier + ", 密码=" + credential);
        // 实际逻辑:验证用户名和密码
        return true;
    }
}

// 短信验证码登录策略
@Component("smsLogin")
public class SmsLoginStrategy implements LoginStrategy {
    @Override
    public boolean login(String identifier, String credential) {
        System.out.println("短信登录: 手机号=" + identifier + ", 验证码=" + credential);
        // 实际逻辑:验证手机号和验证码
        return true;
    }
}

// 第三方OAuth登录策略
@Component("oauthLogin")
public class OAuthLoginStrategy implements LoginStrategy {
    @Override
    public boolean login(String identifier, String credential) {
        System.out.println("OAuth登录: 用户ID=" + identifier + ", 令牌=" + credential);
        // 实际逻辑:验证OAuth令牌
        return true;
    }
}

// 策略上下文
@Component
public class LoginStrategyContext {

    private final Map<String, LoginStrategy> strategyMap;

    @Autowired
    public LoginStrategyContext(Map<String, LoginStrategy> strategyMap) {
        this.strategyMap = strategyMap;
    }

    public boolean login(String type, String identifier, String credential) {
        LoginStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的登录方式: " + type);
        }
        return strategy.login(identifier, credential);
    }
}

控制器示例

java 复制代码
@RestController
@RequestMapping("/auth")
public class LoginController {

    @Autowired
    private LoginStrategyContext loginStrategyContext;

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestParam String type,
                                       @RequestParam String identifier,
                                       @RequestParam String credential) {
        boolean success = loginStrategyContext.login(type, identifier, credential);
        if (success) {
            return ResponseEntity.ok("登录成功");
        } else {
            return ResponseEntity.badRequest().body("登录失败");
        }
    }
}

说明

  1. 自动装配 :Spring Boot通过@Component@Autowired自动将所有LoginStrategy实现注入到strategyMap中,以@Componentvalue(如passwordLogin)作为键。
  2. 动态选择 :客户端通过type参数(如passwordLoginsmsLogin)选择策略,上下文类根据type获取对应的策略并执行。
  3. 扩展性 :若需添加新的登录方式(如扫码登录),只需创建新的LoginStrategy实现并用@Component注解,无需修改现有代码。

运行示例

bash 复制代码
curl -X POST "http://localhost:8080/auth/login?type=passwordLogin&identifier=user1&credential=pass123"
# 输出: 密码登录: 用户名=user1, 密码=pass123
# 响应: 登录成功

curl -X POST "http://localhost:8080/auth/login?type=smsLogin&identifier=1234567890&credential=123456"
# 输出: 短信登录: 手机号=1234567890, 验证码=123456
# 响应: 登录成功

6. 策略模式的优缺点

优点

  1. 解耦逻辑:将算法或行为独立封装,降低客户端与具体实现的耦合。
  2. 易于扩展:新增策略只需实现接口,无需修改现有代码,符合开闭原则。
  3. 避免条件分支 :用多态替代if/elseswitch,代码更简洁。
  4. 灵活切换:支持运行时动态选择策略,适应复杂业务场景。

缺点

  1. 类数量增加:每种策略都需要一个类,可能导致类爆炸。
  2. 客户端复杂性:客户端需要了解策略的用途和适用场景。
  3. 配置开销:在Spring中,需通过注解或配置管理策略,增加少量配置成本。

7. 总结

策略模式是一种优雅的"运行时行为替换"实践,通过接口隔离和多态实现逻辑与行为的解耦。在Spring和Spring Boot中,策略模式与IoC容器和自动装配机制完美结合,广泛应用于Bean实例化、认证授权、事件处理等核心功能。面对业务规则频繁变更的系统,策略模式不仅让代码更清晰、可测试,还赋予系统强大的演化能力。开发者应熟练掌握策略模式,并结合Spring的特性,构建可插拔、可扩展的系统架构。

(对您有帮助 && 觉得我总结的还行) -> 受累点个免费的赞👍,谢谢

相关推荐
晚安code2 小时前
干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合
后端·设计模式
憧憬成为java架构高手的小白4 小时前
黑马八股--spring框架学习
java·学习·spring
Nuanyt8 小时前
SSM 学习记录 第二部分 Spring整合Mybatis&Junit AOP核心概念 Spring事务管理 SpringMVC 请求与响应 Rest风格
java·spring·junit·mybatis·restful
米码收割机8 小时前
【SSM】Spring MVC_MyBatis SSM商城系统(源码+论文)【独一无二】
spring·mvc·mybatis
用户3126874877209 小时前
@Autowired 注入的是代理对象?Spring AOP 代理选择原理一次讲透
java·spring
小罗水9 小时前
第3章 从单体项目到 Spring Cloud 微服务
spring·spring cloud·微服务
workflower10 小时前
从幻觉,到现实
人工智能·深度学习·机器学习·设计模式·机器人
Bug收容所10 小时前
12305项目学习day5
java·spring boot·redis·mysql·spring·rocketmq
lichuangcsdn10 小时前
【Spring AI 学习(三)】实现简单的对话
java·人工智能·学习·spring·spring ai
长不胖的路人甲10 小时前
Spring Bean 完整生命周期
java·spring·rpc