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的特性,构建可插拔、可扩展的系统架构。

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

相关推荐
.生产的驴7 分钟前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
时间之城19 分钟前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
Aniugel2 小时前
JavaScript高级面试题
javascript·设计模式·面试
不当菜虚困2 小时前
JAVA设计模式——(四)门面模式
java·开发语言·设计模式
Niuguangshuo2 小时前
Python设计模式:MVC模式
python·设计模式·mvc
Lei活在当下2 小时前
【现代 Android APP 架构】01. APP 架构综述
android·设计模式·架构
前端大白话2 小时前
震惊!90%前端工程师都踩过的坑!computed属性vs methods到底该怎么选?一文揭秘高效开发密码
前端·vue.js·设计模式
前端大白话3 小时前
前端必看!figure标签在响应式图片排版中的王炸操作,grid/flex布局实战指南
前端·设计模式·html
ApeAssistant3 小时前
Spring + 设计模式 (十四) 行为型 - 观察者模式
spring·设计模式