Spring Boot 实战:拒绝面条代码,用设计模式重构复杂业务系统
在企业级应用开发中,随着业务复杂度的提升,代码往往容易变成难以维护的“面条代码”(Spaghetti Code)。大量的 if-else 嵌套、重复的样板代码、紧耦合的模块依赖,都是系统腐化的征兆。
本文将以一个真实的 “多云文件存储系统”(支持 MinIO、阿里云 OSS、本地存储等多种策略)为例,深度解析 8 种常用设计模式 的落地场景与实战代码。
第一部分:核心架构的“三剑客”
这三种模式组合在一起,构成了高扩展性系统的基石。它们解决了“怎么选策略”、“怎么消除重复”、“怎么解耦”的问题。
1. 策略模式 (Strategy Pattern)
场景:系统需要支持多种文件存储方式(MinIO, Aliyun, Tencent, Local…),且未来可能随时增加新的厂商。
痛点:如果不使用模式,代码里会充斥着 if (type.equals("ALIYUN")) { ... } else if (...)。每次接新厂商都要改主流程代码,违反“开闭原则”。
实战:定义统一接口,不同厂商各自实现。
// 1. 统一接口
public interface StorageStrategy {
UploadResult upload(StorageConfig config, MultipartFile file, String fileKey);
}
// 2. 具体策略实现 (MinIO)
@Component
public class MinioStorageStrategy implements StorageStrategy {
public UploadResult upload(...) { /* MinIO 上传逻辑 */ }
}
// 3. 具体策略实现 (阿里云)
@Component
public class AliyunStorageStrategy implements StorageStrategy {
public UploadResult upload(...) { /* OSS 上传逻辑 */ }
}
2. 工厂模式 (Factory Pattern)
场景:策略写好了,但 Service 层在运行时该怎么拿到正确的策略实例?
痛点:Service 层依然需要写 switch-case 来判断创建哪个对象。
实战:利用 Spring 的依赖注入特性,构建一个策略工厂。
@Component
public class StorageStrategyFactory {
// 核心:利用 Map 自动注入所有策略
private final Map<String, StorageStrategy> strategyMap;
public StorageStrategyFactory(List<StorageStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(StorageStrategy::getType, Function.identity()));
}
public StorageStrategy getStrategy(String type) {
return strategyMap.get(type); // O(1) 时间复杂度获取策略
}
}
3. 模板方法模式 (Template Method Pattern)
场景:无论是 MinIO 还是阿里云,上传前都需要“检查配置类型”,上传时都需要“获取客户端连接(带缓存)”。
痛点:每个策略类里都写一遍缓存逻辑和类型强转逻辑,代码重复率极高。
实战:定义一个抽象父类,规定好“算法骨架”,将具体步骤延迟到子类实现。
public abstract class AbstractCloudStrategy<T extends StorageConfig, C> implements StorageStrategy {
// 模板方法:定义了标准流程
@Override
public UploadResult upload(StorageConfig config, MultipartFile file, String fileKey) {
// 1. 校验配置类型 (通用逻辑)
checkConfigType(config);
// 2. 获取客户端 (通用缓存逻辑)
C client = getClient(config);
// 3. 执行上传 (差异化逻辑,交给子类)
return doUpload(config, client, file, fileKey);
}
// 留给子类实现的抽象方法
protected abstract UploadResult doUpload(T config, C client, ...);
}
第二部分:让系统更健壮的“增强模式”
当核心上传功能完成后,我们往往需要处理校验、后续动作、监控等需求。以下模式能让你的代码逻辑清晰、互不干扰。
4. 责任链模式 (Chain of Responsibility)
场景:文件上传前,需要进行一系列复杂的校验:
- 文件是否为空?
- 文件大小是否超限?
- 文件后缀是否在白名单内?
- 用户当天的上传流量是否超标?
痛点:在 Service 里写一堆 if (check1) { if (check2) ... },逻辑臃肿,且难以调整校验顺序。
实战:将每个校验逻辑封装成一个 Filter,链式执行。
public interface FileUploadFilter {
boolean doFilter(MultipartFile file, User user);
}
@Service
public class FileUploadService {
@Autowired
private List<FileUploadFilter> filters; // 注入所有校验器
public void upload(MultipartFile file) {
// 链式调用
for (FileUploadFilter filter : filters) {
if (!filter.doFilter(file, currentUser)) {
throw new BusinessException("校验不通过");
}
}
// ... 执行上传
}
}
5. 观察者模式 (Observer Pattern)
场景:文件上传成功后,系统需要执行一系列“副作用”操作:
- 保存文件记录到数据库。
- 异步生成图片缩略图。
- 对图片进行 AI 鉴黄审核。
- 给用户发送“上传成功”通知。
痛点:如果在 upload 方法后直接调用 thumbnailService.create(), auditService.check(),会导致上传接口响应极慢,且上传服务与下游业务强耦合。
实战:使用 Spring Event 实现发布-订阅。
// 1. 定义事件
public class FileUploadSuccessEvent extends ApplicationEvent {
private FileInfo fileInfo;
// ...
}
// 2. 发布事件 (在上传成功后)
publisher.publishEvent(new FileUploadSuccessEvent(this, fileInfo));
// 3. 监听事件 (解耦的业务逻辑)
@Component
public class ThumbnailListener {
@Async // 异步执行,不阻塞主线程
@EventListener
public void handle(FileUploadSuccessEvent event) {
// 生成缩略图逻辑...
}
}
6. 建造者模式 (Builder Pattern)
场景:构建复杂的配置对象或客户端对象。
实战:你的代码中 MinioClient 的创建就是典型应用。
// 链式调用,清晰易读,避免了超长参数列表的构造函数
MinioClient client = MinioClient.builder()
.endpoint(endpoint)
.credentials(ak, sk)
.region("cn-north-1")
.httpClient(customHttpClient)
.build();
7. 代理模式 (Proxy Pattern) / AOP
场景:需要统计每个上传接口的耗时,或者在上传出错时自动重试,或者统一处理事务。
痛点:不能在每个 upload 方法里都写 long start = System.currentTimeMillis()。
实战:使用 Spring AOP(动态代理)。
@Aspect
@Component
public class PerformanceAspect {
@Around("@annotation(com.example.annotation.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed(); // 执行原方法
long executionTime = System.currentTimeMillis() - start;
log.info(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
8. 适配器模式 (Adapter Pattern)
场景:系统演进,需要对接一个旧的 FTP 服务器,或者一个第三方网盘 SDK。这个 SDK 的方法名是 transferFile(String path),而你的接口定义是 upload(Config, File, Key)。接口不兼容。
实战:创建一个适配器类,把“方头”转成“圆头”。
// 让旧的 FTP 服务也能适配新的 StorageStrategy 体系
@Component
public class FtpStorageAdapter implements StorageStrategy {
private final LegacyFtpClient ftpClient; // 旧的 SDK
@Override
public UploadResult upload(StorageConfig config, MultipartFile file, String fileKey) {
// 在这里做转换工作
ftpClient.connect(config.getHost());
ftpClient.transferFile(fileKey, file.getInputStream());
return new UploadResult(...);
}
}
总结
设计模式不是为了炫技,而是为了解决实际问题:
| 模式 | 核心作用 | 你的系统中的应用 |
|---|---|---|
| 策略模式 | 消除 if-else,易扩展 |
支持 MinIO/OSS/Local 多种存储 |
| 工厂模式 | 解耦创建逻辑 | 根据 type 自动获取策略 Bean |
| 模板方法 | 复用代码骨架 | 父类处理缓存和类型检查,子类只管上传 |
| 责任链 | 逻辑解耦,动态组合 | 文件格式、大小、权限校验 |
| 观察者 | 异步解耦 | 上传成功后生成缩略图、审计、通知 |
| 建造者 | 构建复杂对象 | MinioClient 的构建 |
| 代理(AOP) | 无侵入增强 | 接口耗时统计、全局异常处理 |
评论区