从一堆 if-else 到优雅扩展:Spring Boot 中策略模式的企业级实践

一、为什么需要策略模式?

在企业项目开发中,经常遇到这样的业务:同一个功能,由于业务规则不同,需要有多种实现方式。

例如支付(支付宝、微信、银联)、文件存储(OSS、MinIO、本地)、消息通知(短信、邮件、钉钉),以及本例的邮件发送(QQ、163、Gmail)。

这些业务有一个共同特点:

业务流程相同,但是具体实现不同。

如果不使用设计模式,通常会写出大量 if-else

public void send(String type) {if ("QQ".equals(type)) {// QQ邮箱发送逻辑} else if ("163".equals(type)) {// 163邮箱发送逻辑} else if ("GMAIL".equals(type)) {// Gmail发送逻辑}
}

随着邮箱类型不断增多,这个方法会越来越庞大,每次新增类型都要修改原有代码,明显违反了开闭原则(对扩展开放,对修改关闭)。这时候就可以使用策略模式。


二、策略模式是什么?

策略模式(Strategy Pattern):

定义一组算法或业务规则,将每种实现分别封装起来,并且可以在运行时动态替换。

以前是一个类里面写所有 if-else 逻辑,使用策略模式后,每个策略负责自己的业务,通过统一接口对外暴露。


三、项目中的策略接口

本项目的 EmailStrategy 就是策略抽象,定义了两个方法:发送邮件和获取邮箱类型。

// EmailStrategy.java
package com.example.email.strategy;import com.example.email.dto.MailRequest;public interface EmailStrategy {void send(MailRequest request);String getType();}

该接口不依赖于任何具体的邮箱实现,只约定行为。


四、不同邮箱实现不同策略

QQ邮箱策略

// QQEmailStrategy.java
package com.example.email.strategy;import com.example.email.dto.MailRequest;
import org.springframework.stereotype.Component;@Component
public class QQEmailStrategy implements EmailStrategy {@Overridepublic void send(MailRequest request) {System.out.println("QQ邮箱发送邮件...");System.out.println("收件人:" + request.getTo());}@Overridepublic String getType() {return "QQ";}}

163邮箱策略

// Email163Strategy.java
package com.example.email.strategy;import com.example.email.dto.MailRequest;
import org.springframework.stereotype.Component;@Component
public class Email163Strategy implements EmailStrategy {@Overridepublic void send(MailRequest request) {System.out.println("163邮箱发送邮件...");}@Overridepublic String getType() {return "163";}}

Gmail策略

// GmailStrategy.java
package com.example.email.strategy;import com.example.email.dto.MailRequest;
import org.springframework.stereotype.Component;@Component
public class GmailStrategy implements EmailStrategy {@Overridepublic void send(MailRequest request) {System.out.println("Gmail发送邮件...");}@Overridepublic String getType() {return "GMAIL";}}

每个策略都是一个独立的 @Component,由 Spring 统一管理。新增邮箱时只需新增一个 EmailStrategy 实现类即可,无需修改任何已有代码。


五、工厂模式:如何选择具体策略

策略接口解决了"每种邮箱如何发送"的问题,但业务调用 service.send(request) 时,系统怎么知道选择哪个策略?

通常配合工厂模式解决。

// EmailStrategyFactory.java
package com.example.email.factory;import com.example.email.strategy.EmailStrategy;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;@Component
public class EmailStrategyFactory {private final Map<String, EmailStrategy> strategyMap;public EmailStrategyFactory(List<EmailStrategy> strategies) {this.strategyMap = strategies.stream().collect(Collectors.toMap(EmailStrategy::getType,Function.identity()));}public EmailStrategy getStrategy(String type) {EmailStrategy strategy = strategyMap.get(type);if (strategy == null) {throw new RuntimeException("不支持的邮箱类型:" + type);}return strategy;}}

核心原理:Spring 启动时,会自动将所有 EmailStrategy 实现类注入到 List<EmailStrategy> 中,工厂将其转换为 Map<type, strategy> 结构。

调用时只需:

EmailStrategy strategy = factory.getStrategy("QQ");
strategy.send(request);

业务代码完全不知道具体实现类,只通过接口交互。


六、模板模式:固定发送流程

邮件发送的完整流程是固定的,但具体发送行为是变化的。固定部分适合用模板模式封装。

// AbstractEmailTemplate.java
package com.example.email.service;import com.example.email.dto.MailRequest;public abstract class AbstractEmailTemplate {public final void send(MailRequest request) {validate(request);beforeSend(request);doSend(request);afterSend(request);}protected void validate(MailRequest request) {if (request.getTo() == null || request.getTo().isEmpty()) {throw new RuntimeException("收件人不能为空");}}protected void beforeSend(MailRequest request) {System.out.println("开始发送邮件");}protected abstract void doSend(MailRequest request);protected void afterSend(MailRequest request) {System.out.println("邮件发送结束");}}

send() 方法用 final 修饰,确保子类不能修改流程顺序。doSend() 是抽象方法,由子类实现。


七、服务层:模板 + 策略 + 工厂组合

// EmailSendService.java
package com.example.email.service;import com.example.email.dto.MailRequest;
import com.example.email.factory.EmailStrategyFactory;
import com.example.email.strategy.EmailStrategy;
import org.springframework.stereotype.Service;@Service
public class EmailSendService extends AbstractEmailTemplate {private final EmailStrategyFactory factory;public EmailSendService(EmailStrategyFactory factory) {this.factory = factory;}@Overrideprotected void doSend(MailRequest request) {EmailStrategy strategy = factory.getStrategy(request.getEmailType());strategy.send(request);}}

EmailSendService 继承模板,实现 doSend() —— 内部通过工厂获取对应策略执行发送。三者协作关系如下:

Controller|v
EmailSendService.send(request)        ← 模板模式(固定流程)|+--- validate(request)            ← 参数校验+--- beforeSend(request)          ← 前置日志+--- doSend(request)              ← 抽象方法,由子类实现|       ||       v|   EmailStrategyFactory          ← 工厂模式(策略选择)|       ||       v|   QQEmailStrategy / Email163Strategy / GmailStrategy|                                   ← 策略模式(具体发送)+--- afterSend(request)           ← 后置处理

八、AOP 日志增强

在 Spring Boot 中还可以通过 AOP 统一增强日志,而无需改动业务代码。

// EmailLogAspect.java
package com.example.email.aspect;import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class EmailLogAspect {@Before("execution(* com.example.email.service.EmailSendService.send(..))")public void before() {System.out.println("记录邮件发送日志");}@AfterReturning("execution(* com.example.email.service.EmailSendService.send(..))")public void after() {System.out.println("邮件发送成功日志");}}

@Before 在方法执行前记录日志,@AfterReturning 在方法成功返回后记录日志。这种横切关注点与业务完全解耦。


九、Controller 层

// EmailController.java
package com.example.email.controller;import com.example.email.dto.MailRequest;
import com.example.email.service.EmailSendService;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/email")
public class EmailController {private final EmailSendService service;public EmailController(EmailSendService service) {this.service = service;}@PostMapping("/send")public String send(@RequestBody MailRequest request) {service.send(request);return "success";}}

请求体示例:

{"emailType": "QQ","to": "test@qq.com","subject": "测试邮件","content": "hello"
}

十、项目完整结构

email-demo
├── pom.xml
└── src/main/java/com/example/email/├── EmailApplication.java           # Spring Boot 启动类├── controller/EmailController.java # REST 接口入口├── dto/MailRequest.java            # 请求参数 DTO├── strategy/│   ├── EmailStrategy.java          # 策略接口(抽象)│   ├── QQEmailStrategy.java        # QQ 策略(具体实现)│   ├── Email163Strategy.java       # 163 策略│   └── GmailStrategy.java          # Gmail 策略├── factory/│   └── EmailStrategyFactory.java   # 策略工厂├── service/│   ├── AbstractEmailTemplate.java  # 模板模式(固定流程)│   └── EmailSendService.java       # 服务(模板+工厂+策略)└── aspect/└── EmailLogAspect.java         # AOP 日志切面

十一、策略模式优点总结

优点 说明
消除 if-else factory.getStrategy(type) 替代了多重条件判断
开闭原则 新增邮箱只需新增策略类,无需修改核心业务
方便测试 每个策略独立,可单独测试
组合灵活 可配合模板模式、工厂模式、单例模式、AOP 组合使用

十二、适用场景

当业务中出现同一个功能,根据不同条件执行不同逻辑时,就可以考虑策略模式。常见场景包括但不限于:

  • 支付渠道:支付宝、微信、银联
  • 文件存储:OSS、MinIO、本地
  • IoT 协议:MQTT、Modbus、OPC UA
  • 消息通知:短信、邮件、微信、钉钉
  • 第三方 SDK:海康、大华、腾讯云、阿里云

十三、总结

策略模式的核心思想是把变化的业务逻辑封装成不同策略,通过统一接口调用,让系统具备更好的扩展能力

在 Spring Boot 企业项目中,策略模式通常与工厂模式(策略选择)、模板模式(流程固定)、单例模式(对象复用)、AOP(横切增强)组合使用,共同构建出高内聚低耦合的架构。邮件发送只是一个入门案例,但其设计思想可以复用到支付、存储、通信、通知等几乎所有业务场景中。