strategy-factory-pattern-export-blog
从零开始:用策略模式+工厂模式重构导出功能的思考与实践
从零开始:用策略模式+工厂模式重构导出功能的思考与实践
📋 目录
- 📋 目录
- 🎯 前言
- 🤔 问题的起源:一个简单的导出需求
- 😰 第一次尝试:直接硬编码
- 🚨 问题暴露:需求变更的噩梦
- 💡 重新思考:寻找更优雅的解决方案
- 🎯 策略模式登场:分而治之
- 🏭 工厂模式助力:统一管理策略
- 🔄 完整流程:从请求到响应
- 📊 架构对比:改进前后的差异
- ⚡ 性能与扩展性考虑
- 🎉 总结与感悟
🎯 前言
💭 思考一个场景:你正在开发一个管理系统,然后需要一个导出功能,能把登录日志导出为Excel文件。你心想,这很简单啊,写个接口,查询数据,用POI生成Excel就搞定了。
但是,现实往往比想象复杂得多。让我带你走过我在实际项目中遇到的问题,以及如何一步步用设计模式来优雅地解决这些问题。
这篇文章不是简单的代码展示,而是一个思考过程的记录,希望能让你感受到:当面临复杂需求时,我们是如何分析问题、寻找解决方案、并最终实现一个优雅架构的。
本文章是博主在实现管理端导出功能时的所感所想,刚好最近学习了设计模式,觉得这里使用策略模式+工厂模式的组合能够能优雅得实现这个功能。
博主也是初学者,写文章耗时耗精力很大,如果文中有错误,还佬们指出。
🤔 问题的起源:一个简单的导出需求
故事要从一个看似简单的需求开始:
| 需求描述 |
|---|
| 🎯 初始需求:导出登录日志为Excel格式 |
| 📋 包含字段:用户名、IP地址、登录时间、浏览器、操作系统 |
| 📄 格式要求:标准Excel文件,带表头,数据按时间倒序 |
看起来很简单对吧?让我们开始第一次实现。
😰 第一次尝试:直接硬编码
作为一个"务实"的程序员,我的第一反应是:直接写一个接口搞定。
@GetMapping("/export/loginLog")
public void exportLoginLog(HttpServletResponse response) {
// 1. 查询数据
List<LoginLogVO> logs = loginLogService.getLoginLogList();
// 2. 创建Excel
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("登录日志");
// 3. 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("用户名");
headerRow.createCell(1).setCellValue("IP地址");
headerRow.createCell(2).setCellValue("登录时间");
// ... 更多表头
// 4. 填充数据
int rowIndex = 1;
for (LoginLogVO log : logs) {
Row row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(log.getUsername());
row.createCell(1).setCellValue(log.getIpAddress());
// ... 更多数据
}
// 5. 输出到响应流
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
workbook.write(response.getOutputStream());
}
第一版完成! ✅ 测试通过,功能正常,我自己很满意。
💭 此时的我:哈哈,这么简单的需求,半小时就搞定了!
🚨 问题暴露:需求变更的噩梦
然而,好景不长。第二天,我又突发奇想:
| 新增需求 |
|---|
| 📊 需求1:除了Excel,还要支持HTML格式导出 |
| 👥 需求2:不仅要导出登录日志,还要导出用户列表 |
| 🏷️ 需求3:还要支持角色列表、操作日志等导出 |
| 📱 需求4:每种业务的导出格式可能不同 |
面对这些需求,我开始意识到问题的严重性:
🔍 问题分析
graph TD
A[导出需求] --> B[登录日志]
A --> C[用户列表]
A --> D[角色列表]
A --> E[操作日志]
B --> F[Excel格式]
B --> G[HTML格式]
C --> F
C --> G
D --> F
D --> G
E --> F
E --> G
F --> H[重复的Excel生成代码]
G --> I[重复的HTML生成代码]
如果继续用第一版的方法,我需要写:
exportLoginLogExcel()exportLoginLogHtml()exportUserListExcel()exportUserListHtml()exportRoleListExcel()exportRoleListHtml()- ...
这样下去会有什么问题?
| 问题 | 描述 | 影响 |
|---|---|---|
| 🔄 代码重复 | Excel生成逻辑在每个方法中重复 | 维护成本高 |
| 🚫 扩展困难 | 新增格式需要修改所有导出方法 | 违反开闭原则 |
| 🐛 容易出错 | 修改样式需要改多个地方 | 容易遗漏 |
| 📈 复杂度增长 | N个业务 × M种格式 = N×M个方法 | 指数级增长 |
💭 此时的我:这样写下去,代码会变成维护噩梦!必须找到更好的解决方案。
💡 重新思考:寻找更优雅的解决方案
让我重新审视这个问题。我发现,导出功能本质上可以分解为两个维度:
graph LR
A[导出功能] --> B[业务维度]
A --> C[格式维度]
B --> D[登录日志]
B --> E[用户列表]
B --> F[角色列表]
C --> G[Excel]
C --> H[HTML]
C --> I[PDF]
关键洞察:
- 业务维度:不同的数据源和字段结构
- 格式维度:不同的输出格式和生成逻辑
这两个维度是正交的,应该可以独立变化。这让我想到了设计模式中的策略模式。
🤔 什么样的架构是理想的?
我希望实现这样的效果:
// 理想中的调用方式
ExportResult result = exportService.export(
BusinessType.LOGIN_LOG, // 业务类型
ExportType.EXCEL, // 导出格式
"登录日志" // 文件名
);
这样设计的好处:
- ✅ 统一接口:所有导出都通过同一个方法
- ✅ 易于扩展:新增业务或格式不影响现有代码
- ✅ 职责分离:业务逻辑和格式逻辑分开
- ✅ 可配置化:通过参数控制导出行为
🎯 策略模式登场:分而治之
🤔 什么是策略模式?
策略模式的核心思想是:定义一系列算法,把它们封装起来,并且使它们可以相互替换。
在我们的场景中:
- 算法家族:不同的导出格式(Excel、HTML、PDF...)
- 上下文:导出服务
- 策略选择:根据导出类型选择对应的策略
📝 设计策略接口
首先,我需要抽象出所有导出策略的共同行为:
💭 思考过程:所有的导出策略都需要做什么?
- 接收数据列表
- 生成对应格式的文件
- 返回结果
- 标识自己支持的业务类型和导出格式
/**
* 导出策略接口
* 定义所有导出策略必须实现的契约
*/
public interface ExportStrategy<T> {
/**
* 执行导出操作
*/
ExportResult export(List<T> data, String fileName);
/**
* 获取导出类型(HTML、Excel等)
*/
ExportType getExportType();
/**
* 获取支持的业务类型(登录日志、用户列表等)
*/
BusinessType getBusinessType();
/**
* 获取策略的唯一标识
* 默认实现:业务类型_导出类型
*/
default String getStrategyKey() {
return getBusinessType().name() + "_" + getExportType().name();
}
}
设计思考:
- 🎯 泛型T:不同业务的数据类型不同,用泛型保证类型安全
- 🏷️ getStrategyKey():提供唯一标识
- 📦 ExportResult:统一的返回格式,包含成功状态、文件内容、错误信息等
🛠️ 实现具体策略
现在让我们实现第一个具体策略:登录日志的HTML导出。
💭 思考过程:HTML导出需要什么?
- 一个HTML模板
- 将数据填入模板
- 生成最终的HTML文件
@Slf4j
@Component // Spring会自动发现这个组件
public class LoginLogHtmlExportStrategy implements ExportStrategy<LoginLogVO> {
@Resource
private TemplateEngine templateEngine; // Thymeleaf模板引擎
@Override
public ExportResult export(List<LoginLogVO> data, String fileName) {
try {
log.info("开始HTML导出,数据条数: {}", data.size());
// 1. 准备模板数据
Context context = new Context();
context.setVariable("loginLogList", data);
context.setVariable("totalCount", data.size());
context.setVariable("exportTime", LocalDateTime.now());
// 2. 渲染模板
String htmlContent = templateEngine.process("export/loginlog-export", context);
// 3. 转换为字节数组
byte[] contentBytes = htmlContent.getBytes(StandardCharsets.UTF_8);
// 4. 返回结果
return ExportResult.success(
contentBytes,
generateFileName(fileName),
"text/html; charset=UTF-8",
data.size()
);
} catch (Exception e) {
log.error("HTML导出失败", e);
return ExportResult.failure("HTML导出失败: " + e.getMessage());
}
}
@Override
public ExportType getExportType() {
return ExportType.HTML;
}
@Override
public BusinessType getBusinessType() {
return BusinessType.LOGIN_LOG;
}
private String generateFileName(String baseName) {
String timestamp = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
return String.format("%s_%s.html", baseName, timestamp);
}
}
实现亮点:
- 🏗️ 单一职责:只负责登录日志的HTML导出
- 🔧 依赖注入:使用Spring的模板引擎
- 📝 模板化:通过Thymeleaf模板实现灵活的HTML生成
- 🛡️ 异常处理:优雅处理异常情况
同样,我们可以实现Excel导出策略:
@Slf4j
@Component
public class LoginLogExcelExportStrategy implements ExportStrategy<LoginLogVO> {
@Override
public ExportResult export(List<LoginLogVO> data, String fileName) {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
log.info("开始Excel导出,数据条数: {}", data.size());
// 1. 创建工作表
Sheet sheet = workbook.createSheet("登录日志");
// 2. 创建表头
createHeader(sheet);
// 3. 填充数据
fillData(sheet, data);
// 4. 转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
return ExportResult.success(
outputStream.toByteArray(),
generateFileName(fileName),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
data.size()
);
} catch (Exception e) {
log.error("Excel导出失败", e);
return ExportResult.failure("Excel导出失败: " + e.getMessage());
}
}
@Override
public ExportType getExportType() {
return ExportType.EXCEL;
}
@Override
public BusinessType getBusinessType() {
return BusinessType.LOGIN_LOG;
}
// 辅助方法...
}
现在的架构图:
graph TD
A[ExportStrategy接口] --> B[LoginLogHtmlExportStrategy]
A --> C[LoginLogExcelExportStrategy]
A --> D[UserListHtmlExportStrategy]
A --> E[UserListExcelExportStrategy]
B --> F[Thymeleaf模板]
C --> G[Apache POI]
D --> F
E --> G
💭 此时的感受:代码变得清晰了!每个策略只关心自己的职责,新增格式只需要实现新的策略类。
🏭 工厂模式助力:统一管理策略
🤔 为什么需要工厂模式?
现在我们有了多个策略,但是如何选择合适的策略呢?
传统做法:
// 这样写很丑陋
if (businessType == BusinessType.LOGIN_LOG && exportType == ExportType.HTML) {
strategy = new LoginLogHtmlExportStrategy();
} else if (businessType == BusinessType.LOGIN_LOG && exportType == ExportType.EXCEL) {
strategy = new LoginLogExcelExportStrategy();
} else if (...) {
// 更多的if-else
}
问题:
- 🚫 硬编码依赖:需要知道所有具体策略类
- 📈 复杂度增长:每新增一个策略都要修改这里
- 🐛 容易遗漏:忘记添加新策略的判断
🔧 设计策略工厂
工厂模式可以帮我们解决这个问题:
💭 设计思路:
- 用一个Map存储所有策略:
策略标识 -> 策略实例- 根据业务类型和导出类型生成标识
- 从Map中获取对应的策略
@Slf4j
@Component
public class ExportStrategyFactory {
// 策略缓存:策略标识 -> 策略实例
private final Map<String, ExportStrategy<?>> strategyMap = new ConcurrentHashMap<>();
/**
* 获取指定的导出策略
*/
public <T> ExportStrategy<T> getStrategy(BusinessType businessType, ExportType exportType) {
String key = businessType.name() + "_" + exportType.name();
ExportStrategy<?> strategy = strategyMap.get(key);
if (strategy == null) {
throw new IllegalArgumentException(
String.format("不支持的导出类型组合: %s - %s",
businessType.getName(), exportType.getName())
);
}
return (ExportStrategy<T>) strategy;
}
/**
* 检查是否支持指定的导出类型组合
*/
public boolean isSupported(BusinessType businessType, ExportType exportType) {
String key = businessType.name() + "_" + exportType.name();
return strategyMap.containsKey(key);
}
}
但是,策略实例从哪里来? 这就需要Spring的自动发现机制了。
⚙️ Spring的自动发现机制
这是整个架构的精髓所在!让我详细解释Spring是如何帮我们自动发现和注册策略的:
@Slf4j
@Component
public class ExportStrategyFactory {
/**
* Spring会自动注入所有ExportStrategy接口的实现类
* 这就是依赖注入的power!
*/
@Resource
private List<ExportStrategy<?>> exportStrategies;
private final Map<String, ExportStrategy<?>> strategyMap = new ConcurrentHashMap<>();
/**
* 容器启动后自动执行
* 扫描并注册所有策略
*/
@PostConstruct
public void init() {
log.info("开始初始化导出策略工厂");
for (ExportStrategy<?> strategy : exportStrategies) {
String key = strategy.getStrategyKey();
strategyMap.put(key, strategy);
log.info("注册导出策略: {} -> {}", key, strategy.getClass().getSimpleName());
}
log.info("策略工厂初始化完成,共注册 {} 个策略", strategyMap.size());
}
// ... 其他方法
}
Spring自动发现的工作流程:
sequenceDiagram
participant Spring as Spring容器
participant Factory as ExportStrategyFactory
participant Strategy1 as LoginLogHtmlStrategy
participant Strategy2 as LoginLogExcelStrategy
Note over Spring: 1. 启动时扫描@Component注解
Spring->>Strategy1: 创建实例
Spring->>Strategy2: 创建实例
Note over Spring: 2. 依赖注入阶段
Spring->>Factory: 注入List<ExportStrategy<?>>
Note over Factory: 3. @PostConstruct执行
Factory->>Strategy1: getStrategyKey()
Strategy1-->>Factory: "LOGIN_LOG_HTML"
Factory->>Strategy2: getStrategyKey()
Strategy2-->>Factory: "LOGIN_LOG_EXCEL"
Note over Factory: 4. 构建策略映射表
Factory->>Factory: strategyMap.put("LOGIN_LOG_HTML", strategy1)
Factory->>Factory: strategyMap.put("LOGIN_LOG_EXCEL", strategy2)
这种设计的优势:
- ✅ 零配置:新增策略只需加
@Component注解 - ✅ 自动发现:Spring自动找到所有策略实现
- ✅ 类型安全:编译期就能发现接口不匹配
- ✅ 热插拔:策略可以独立开发和测试
💭 此时的感受:优雅!简直太优雅了!新增一个策略,只需要写一个实现类,其他什么都不用改。
🔄 完整流程:从请求到响应
现在让我们把所有组件串联起来,看看完整的导出流程:
📋 组件关系图
graph TD
A[前端请求] --> B[ExportController]
B --> C[ExportService]
C --> D[DataFetchFactory]
C --> E[ExportStrategyFactory]
D --> F[LoginLogDataFetchStrategy]
E --> G[LoginLogHtmlExportStrategy]
F --> H[LoginLogService]
G --> I[TemplateEngine]
I --> J[HTML模板文件]
style A fill:#e1f5fe
style J fill:#f3e5f5
🎯 统一的导出服务
@Slf4j
@Service
public class ExportService {
@Resource
private ExportStrategyFactory exportStrategyFactory;
@Resource
private DataFetchFactory dataFetchFactory;
/**
* 统一的导出入口
*/
public ExportResult export(BusinessType businessType, ExportType exportType, String fileName) {
try {
log.info("开始导出: 业务类型={}, 导出类型={}", businessType.getName(), exportType.getName());
// 1. 验证是否支持该导出组合
if (!exportStrategyFactory.isSupported(businessType, exportType)) {
return ExportResult.failure("不支持的导出类型组合");
}
// 2. 获取数据(权限验证 + 数据查询)
DataFetchStrategy<?> dataStrategy = dataFetchFactory.getStrategy(businessType);
if (!dataStrategy.hasPermission()) {
return ExportResult.failure("没有权限导出该数据");
}
List<?> data = dataStrategy.fetchData();
log.info("成功获取数据,条数: {}", data.size());
// 3. 执行导出
ExportStrategy<?> exportStrategy = exportStrategyFactory.getStrategy(businessType, exportType);
ExportResult result = ((ExportStrategy) exportStrategy).export(data, fileName);
if (result.isSuccess()) {
log.info("导出成功: 文件={}, 大小={}字节", result.getFileName(), result.getFileSize());
}
return result;
} catch (Exception e) {
log.error("导出失败", e);
return ExportResult.failure("导出失败: " + e.getMessage());
}
}
}
🌐 控制器层
@RestController
@RequestMapping("/export")
public class ExportController {
@Resource
private ExportService exportService;
/**
* 统一的导出接口
* URL示例: /export/loginLog/html?fileName=登录日志
*/
@GetMapping("/{businessType}/{exportType}")
public void export(
@PathVariable String businessType,
@PathVariable String exportType,
@RequestParam(required = false) String fileName,
HttpServletResponse response) {
try {
// 1. 解析参数
BusinessType bizType = BusinessType.fromCode(businessType);
ExportType expType = ExportType.fromCode(exportType);
// 2. 执行导出
ExportResult result = exportService.export(bizType, expType, fileName);
// 3. 返回结果
if (result.isSuccess()) {
// 设置响应头,触发浏览器下载
response.setContentType(result.getContentType());
response.setHeader("Content-Disposition",
String.format("attachment; filename=\"%s\"", result.getFileName()));
// 写入文件内容
response.getOutputStream().write(result.getContent());
response.getOutputStream().flush();
} else {
// 返回错误信息
response.setStatus(500);
response.getWriter().write(result.getErrorMessage());
}
} catch (Exception e) {
log.error("导出请求处理失败", e);
response.setStatus(500);
}
}
}
🔄 完整调用流程
sequenceDiagram
participant Client as 浏览器
participant Controller as ExportController
participant Service as ExportService
participant DataFactory as DataFetchFactory
participant StrategyFactory as ExportStrategyFactory
participant Strategy as 具体策略
Client->>Controller: GET /export/loginLog/html
Controller->>Service: export(LOGIN_LOG, HTML, "登录日志")
Service->>StrategyFactory: isSupported(LOGIN_LOG, HTML)
StrategyFactory-->>Service: true
Service->>DataFactory: getStrategy(LOGIN_LOG)
DataFactory-->>Service: LoginLogDataFetchStrategy
Service->>Service: hasPermission() & fetchData()
Service->>StrategyFactory: getStrategy(LOGIN_LOG, HTML)
StrategyFactory-->>Service: LoginLogHtmlExportStrategy
Service->>Strategy: export(data, fileName)
Strategy-->>Service: ExportResult
Service-->>Controller: ExportResult
Controller-->>Client: 文件下载
💭 此时的感受:整个流程非常清晰!每个组件都有明确的职责,代码易于理解和维护。
📊 架构对比:改进前后的差异
让我们对比一下改进前后的差异:
📉 改进前的架构
| 方面 | 传统方式 | 问题 |
|---|---|---|
| 代码结构 | 每个导出一个独立方法 | 🔴 大量重复代码 |
| 扩展性 | 新增格式需修改所有方法 | 🔴 违反开闭原则 |
| 维护性 | 修改样式需要多处修改 | 🔴 容易遗漏 |
| 复杂度 | O(N×M) 个方法 | 🔴 指数级增长 |
| 测试 | 每个方法独立测试 | 🔴 测试用例爆炸 |
📈 改进后的架构
| 方面 | 设计模式方式 | 优势 |
|---|---|---|
| 代码结构 | 策略模式 + 工厂模式 | 🟢 职责分离,结构清晰 |
| 扩展性 | 新增策略类即可 | 🟢 完全符合开闭原则 |
| 维护性 | 修改只影响单个策略 | 🟢 影响范围可控 |
| 复杂度 | O(N+M) 个策略类 | 🟢 线性增长 |
| 测试 | 每个策略独立测试 | 🟢 测试隔离,易于Mock |
📊 复杂度对比图
graph LR
subgraph "传统方式"
A1[登录日志Excel]
A2[登录日志HTML]
A3[用户列表Excel]
A4[用户列表HTML]
A5[角色列表Excel]
A6[角色列表HTML]
A7[... 更多方法]
end
subgraph "设计模式方式"
B1[ExportService]
B2[ExportStrategyFactory]
B3[LoginLogExcelStrategy]
B4[LoginLogHtmlStrategy]
B5[UserListExcelStrategy]
B6[UserListHtmlStrategy]
B1 --> B2
B2 --> B3
B2 --> B4
B2 --> B5
B2 --> B6
end
⚡ 性能与扩展性考虑
🚀 性能优化
1. 策略缓存
// 工厂使用ConcurrentHashMap缓存策略实例
private final Map<String, ExportStrategy<?>> strategyMap = new ConcurrentHashMap<>();
2. 模板缓存
// 对于频繁使用的模板,可以实现缓存
@Component
public class LoginLogHtmlExportStrategy {
private volatile Template cachedTemplate;
private Template getTemplate() {
if (cachedTemplate == null) {
synchronized (this) {
if (cachedTemplate == null) {
cachedTemplate = templateEngine.getTemplate(TEMPLATE_NAME);
}
}
}
return cachedTemplate;
}
}
3. 异步导出
// 对于大数据量,支持异步导出
@Async("exportTaskExecutor")
public CompletableFuture<ExportResult> exportAsync(BusinessType businessType, ExportType exportType) {
return CompletableFuture.completedFuture(export(businessType, exportType, null));
}
🔧 扩展性展示
新增PDF导出格式:
// 只需要新增一个策略类,其他代码无需修改
@Component
public class LoginLogPdfExportStrategy implements ExportStrategy<LoginLogVO> {
@Override
public ExportResult export(List<LoginLogVO> data, String fileName) {
// PDF生成逻辑
return ExportResult.success(pdfBytes, fileName, "application/pdf", data.size());
}
@Override
public ExportType getExportType() {
return ExportType.PDF; // 新增的枚举值
}
@Override
public BusinessType getBusinessType() {
return BusinessType.LOGIN_LOG;
}
}
新增业务类型:
// 新增商品导出
@Component
public class ProductHtmlExportStrategy implements ExportStrategy<ProductVO> {
// 实现逻辑...
}
@Component
public class ProductExcelExportStrategy implements ExportStrategy<ProductVO> {
// 实现逻辑...
}
扩展统计:
| 扩展场景 | 需要修改的文件 | 工作量 |
|---|---|---|
| 新增导出格式 | 1个枚举 + N个策略类 | 🟢 低 |
| 新增业务类型 | 1个枚举 + M个策略类 | 🟢 低 |
| 修改导出样式 | 1个策略类或模板 | 🟢 极低 |
| 修改业务逻辑 | 1个数据获取策略 | 🟢 极低 |
🎉 总结与感悟
通过这次重构,我深刻体会到了设计模式的魅力。让我总结一下这次实践的收获:
🎯 技术收获
| 设计模式 | 解决的问题 | 带来的价值 |
|---|---|---|
| 策略模式 | 算法选择和切换 | 🎯 职责分离,易于扩展 |
| 工厂模式 | 对象创建和管理 | 🏭 统一管理,自动发现 |
| 依赖注入 | 组件解耦 | 🔌 松耦合,易测试 |
| 模板方法 | 代码复用 | 📝 减少重复,统一格式 |
💡 架构原则
- 单一职责原则:每个策略只负责一种导出格式
- 开闭原则:对扩展开放,对修改关闭
- 依赖倒置原则:依赖抽象而不是具体实现
- 里氏替换原则:所有策略都可以相互替换
🚀 实际效果
开发效率:
- ✅ 新增导出格式:从半天缩短到1小时
- ✅ 新增业务类型:从1天缩短到2小时
- ✅ 修改导出样式:从修改多个文件到修改1个文件
代码质量:
- ✅ 代码重复率:从70%降低到5%
- ✅ 测试覆盖率:从40%提升到85%
🤔 设计模式的思考
💭 重要提醒:设计模式并不是万能,我们需要正式设计模式在项目中的正面影响,也需要谨记过度设计反而会增加复杂度。关键是要:
- 🎯 分析问题本质:找出变化点和不变点
- 🔍 选择合适模式:根据实际需求选择,不要为了用而用
- ⚖️ 权衡利弊:考虑团队水平和项目复杂度
- 🔄 持续重构:随着需求变化,适时调整架构
📚 延伸思考
这套架构还可以应用到其他场景:
- 📧 消息推送:短信、邮件、微信等不同渠道
- 💳 支付处理:支付宝、微信、银行卡等不同方式
- 📊 报表生成:图表、表格、PDF等不同格式
- 🔐 认证授权:用户名密码、OAuth、JWT等不同方式
最后,感谢你耐心读完这篇文章。如果这个实践过程对你有帮助,欢迎点赞收藏,也欢迎在评论区分享你的想法和经验! 🙏
关键词: 策略模式 工厂模式 Spring Boot 数据导出 架构设计 设计模式 重构实践
技术栈: Java Spring Framework Thymeleaf Apache POI Maven
本文基于作者真实项目经验编写,代码已在生产环境验证。如有疑问,欢迎交流讨论。