侧边栏壁纸
  • 累计撰写 32 篇文章
  • 累计创建 38 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

拒绝面条代码,用设计模式重构复杂业务系统

一杯香梨
2026-01-09 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

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)

场景:文件上传前,需要进行一系列复杂的校验:

  1. 文件是否为空?
  2. 文件大小是否超限?
  3. 文件后缀是否在白名单内?
  4. 用户当天的上传流量是否超标?

痛点:在 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)

场景:文件上传成功后,系统需要执行一系列“副作用”操作:

  1. 保存文件记录到数据库。
  2. 异步生成图片缩略图。
  3. 对图片进行 AI 鉴黄审核。
  4. 给用户发送“上传成功”通知。

痛点:如果在 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) 无侵入增强 接口耗时统计、全局异常处理
0

评论区