泛型+函数式:让策略模式不再是复制粘贴地狱
从200行重复代码到30行优雅实现,泛型+函数式落地
导入模块的"复制粘贴地狱"
血泪史:每加一个导入类型,我都要复制200行代码
前段时间我写了一个会员导入模块
导入模块的设计架构图
graph TD
A[调用方] --> B[ImportService]
B --> C{根据类型路由}
C --> D[MemberImportStrategy
215行] C --> E[JiayouMemberImportStrategy
203行] C --> F[TypeCardImportStrategy
198行] D --> D1[解析Excel 20行] D --> D2[解析参数 15行] D --> D3[构建对象 25行] D --> D4[执行导入 10行] D --> D5[处理失败 15行] D --> D6[上传OSS 50行] D --> D7[异常处理 15行] E --> E1[解析Excel 20行] E --> E2[解析参数 15行] E --> E3[构建对象 25行] E --> E4[执行导入 10行] E --> E5[处理失败 15行] E --> E6[上传OSS 50行] E --> E7[异常处理 15行] F --> F1[解析Excel 20行] F --> F2[解析参数 15行] F --> F3[构建对象 25行] F --> F4[执行导入 10行] F --> F5[处理失败 15行] F --> F6[上传OSS 50行] F --> F7[异常处理 15行] style D fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style E fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style F fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style D1 fill:#fcc2c2 style D2 fill:#fcc2c2 style D3 fill:#fcc2c2 style D4 fill:#fcc2c2 style D5 fill:#fcc2c2 style D6 fill:#fcc2c2 style D7 fill:#fcc2c2 style E1 fill:#fcc2c2 style E2 fill:#fcc2c2 style E3 fill:#fcc2c2 style E4 fill:#fcc2c2 style E5 fill:#fcc2c2 style E6 fill:#fcc2c2 style E7 fill:#fcc2c2 style F1 fill:#fcc2c2 style F2 fill:#fcc2c2 style F3 fill:#fcc2c2 style F4 fill:#fcc2c2 style F5 fill:#fcc2c2 style F6 fill:#fcc2c2 style F7 fill:#fcc2c2
215行] C --> E[JiayouMemberImportStrategy
203行] C --> F[TypeCardImportStrategy
198行] D --> D1[解析Excel 20行] D --> D2[解析参数 15行] D --> D3[构建对象 25行] D --> D4[执行导入 10行] D --> D5[处理失败 15行] D --> D6[上传OSS 50行] D --> D7[异常处理 15行] E --> E1[解析Excel 20行] E --> E2[解析参数 15行] E --> E3[构建对象 25行] E --> E4[执行导入 10行] E --> E5[处理失败 15行] E --> E6[上传OSS 50行] E --> E7[异常处理 15行] F --> F1[解析Excel 20行] F --> F2[解析参数 15行] F --> F3[构建对象 25行] F --> F4[执行导入 10行] F --> F5[处理失败 15行] F --> F6[上传OSS 50行] F --> F7[异常处理 15行] style D fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style E fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style F fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style D1 fill:#fcc2c2 style D2 fill:#fcc2c2 style D3 fill:#fcc2c2 style D4 fill:#fcc2c2 style D5 fill:#fcc2c2 style D6 fill:#fcc2c2 style D7 fill:#fcc2c2 style E1 fill:#fcc2c2 style E2 fill:#fcc2c2 style E3 fill:#fcc2c2 style E4 fill:#fcc2c2 style E5 fill:#fcc2c2 style E6 fill:#fcc2c2 style E7 fill:#fcc2c2 style F1 fill:#fcc2c2 style F2 fill:#fcc2c2 style F3 fill:#fcc2c2 style F4 fill:#fcc2c2 style F5 fill:#fcc2c2 style F6 fill:#fcc2c2 style F7 fill:#fcc2c2
目前已有的3个导入策略:
MemberImportStrategy
(普通会员导入):215行JiayouMemberImportStrategy
(加油会员导入):203行TypeCardImportStrategy
(类型卡导入):198行
你会发现,这三个类的代码长得一模一样!
ini
@Component("MemberImportStrategy")
public class MemberImportStrategy implements ImportStrategy {
@Override
public void importData(ImportParam param) {
List<CustomerImportResult> failList = Lists.newArrayList();
try {
// 1. 解析Excel(20行)
List<CustomerImportParam> memberList = fileExcelService.getMemberInfoByExcel(
param,
CustomerImportParam.class // 每个策略这里不一样
);
// 2. 解析业务参数(15行)
JSONObject jsonObject = JSONObject.parseObject(param.getBussesParams());
String storeId = jsonObject.getString("storeId"); // 字符串key!!!
String storeName = jsonObject.getString("storeName"); // 一个字母拼错,运行时才报错
// 3. 构建导入参数(25行)
MemberCustomerImportParam customerImportParam = new MemberCustomerImportParam();
customerImportParam.setStoreId(storeId);
customerImportParam.setStoreName(storeName);
customerImportParam.setPlatformId(param.getPlatformId());
customerImportParam.setOrgId(param.getOrgId());
customerImportParam.setMerchantId(param.getMerchantId());
// ... 一堆set,每个策略都一样
// 4. 执行导入(10行)
MemberCustomerImportVerifyResult importResult =
memberImportService.importCustomerList(customerImportParam);
// 5. 处理失败记录(15行)
failList = importResult.getFailList();
if (CollUtil.isNotEmpty(failList)) {
uploadFailFile(fileParam, failList);
}
} catch (Exception e) {
// 6. 异常处理(15行)
CustomerImportResult failResult = new CustomerImportResult();
failResult.setFailReason(e.getMessage());
uploadFailFile(fileParam, failList);
throw MemberException.INVALID_PARAM_ERROR.newInstance("解析会员数据异常");
}
}
// 7. 上传失败文件(50行)
private void uploadFailFile(MemberImportFileParam param, List<CustomerImportResult> errorList) {
String fileName = FileTemplateConstants.MEMBER_IMPORT_ERROR_FILE_PREFIX + param.getFname();
String filePath = FileTemplateConstants.FILE_TEMP_DIR + fileName;
try {
// 硬编码字段映射(30行)
ExcelWriter writer = ExcelUtil.getWriter(filePath);
writer.addHeaderAlias("cardNo", "会员卡号"); // 每个策略都要重写一遍
writer.addHeaderAlias("name", "姓名"); // 新增字段?改3个地方!
writer.addHeaderAlias("phone", "手机号");
writer.addHeaderAlias("gender", "性别");
writer.addHeaderAlias("birthday", "出生日期");
// ... 15个字段映射
writer.write(errorList);
// 上传OSS(20行)
OSSClient client = AliyunOssUtil.getAliOssConfigOut(...);
boolean success = AliyunOssUtil.uploadFile(...);
// ...
} finally {
FileUtil.del(filePath);
}
}
}
加油会员导入?复制粘贴,改几个类名:
kotlin
@Component("JiayouMemberImportStrategy")
public class JiayouMemberImportStrategy implements ImportStrategy {
// ... 完全相同的代码,只是类型换成 JiayouMemberImportParam
}
当初设计这个模块时,我还想说"用了策略模式,每个导入类型实现自己的业务,我也不去约束,调用方只需要调用对应的策略就行,还挺好"
但是我忽略了设计模式的本质:规范行为,而不是规范接口。
- 我只定义了一个空接口
ImportStrategy
,却没有抽象出通用流程 - 我让每个策略"自由发挥",结果就是每个人都写了一遍相同的代码
- 我以为"继承接口"就是策略模式,其实这只是"换汤不换药"的复制粘贴
真正的策略模式应该是:
- 基类封装通用逻辑(模板方法模式)
- 子类只实现差异化部分(策略模式)
- 泛型保证类型安全(编译期检查)
现在想想,我当初只学会了策略模式的"形",没学会"神"。
导入模块的核心问题
对比代码,大家就会发现了4个比较大的问题:
java
// 1. 接口没有泛型约束
public interface ImportStrategy {
void importData(ImportParam param); // 返回void,啥都不知道
}
// 2. 没有抽象基类复用代码
@Component("MemberImportStrategy")
public class MemberImportStrategy implements ImportStrategy {
// 每个策略从零开始写200行
}
// 3. 硬编码类型
List<CustomerImportParam> memberList = fileExcelService.getMemberInfoByExcel(
param,
CustomerImportParam.class // 固定类型,换个导入类型就得重写
);
// 4. 字符串key取值
String storeId = jsonObject.getString("storeId"); // 拼错了编译期不报错
问题一目了然:
- 每个策略都是独立的150行重复代码
- 没有基类抽象通用逻辑
- 修改一处逻辑 = 改3个地方
- 新增策略 = 复制粘贴200行
实践:用泛型+函数式设计导出模块
后来要做导出功能,吸取了导入模块的教训,这次用泛型+函数式重新设计。
设计思路
核心目标:规范实现类的行为,策略实现类只需要关注自己的业务流程
-
泛型抽象基类
BaseExportStrategy
- 封装通用流程:分页查询、Excel生成、ZIP压缩、OSS上传、异常处理
- 使用泛型方法适配不同数据类型
-
泛型函数式接口
PageQueryCallback<T>
:定义查询逻辑DataConvertCallback<T, R>
:定义转换逻辑(T → R)
-
具体策略只需2件事
- Lambda:怎么查数据
- Lambda:怎么转换VO(留的口子,没有特殊直接可以使用mapstruct转换更方便)
导出模块的设计架构图
graph TD
A[调用方] --> B[ExportService]
B --> C{根据类型路由}
C --> D[StoreConsumptionRecordStrategy
30行] C --> E[StoreRechargeRecordStrategy
30行] C --> F[StoreReconciliationStrategy
30行] D --> G[BaseExportStrategy
泛型抽象基类 150行] E --> G F --> G G --> G1[分页查询 Lambda回调] G --> G2[数据转换 Lambda回调] G --> G3[Excel生成 通用] G --> G4[ZIP压缩 通用] G --> G5[OSS上传 通用] G --> G6[异常处理 通用] D -.传入Lambda.-> D1[查询逻辑 10行] D -.传入Lambda.-> D2[转换逻辑 5行] E -.传入Lambda.-> E1[查询逻辑 10行] E -.传入Lambda.-> E2[转换逻辑 5行] F -.传入Lambda.-> F1[查询逻辑 10行] F -.传入Lambda.-> F2[转换逻辑 5行] style D fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style E fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style F fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style G fill:#339af0,stroke:#1c7ed6,stroke-width:3px,color:#fff style G1 fill:#74c0fc style G2 fill:#74c0fc style G3 fill:#a5d8ff style G4 fill:#a5d8ff style G5 fill:#a5d8ff style G6 fill:#a5d8ff style D1 fill:#b2f2bb style D2 fill:#b2f2bb style E1 fill:#b2f2bb style E2 fill:#b2f2bb style F1 fill:#b2f2bb style F2 fill:#b2f2bb
30行] C --> E[StoreRechargeRecordStrategy
30行] C --> F[StoreReconciliationStrategy
30行] D --> G[BaseExportStrategy
泛型抽象基类 150行] E --> G F --> G G --> G1[分页查询 Lambda回调] G --> G2[数据转换 Lambda回调] G --> G3[Excel生成 通用] G --> G4[ZIP压缩 通用] G --> G5[OSS上传 通用] G --> G6[异常处理 通用] D -.传入Lambda.-> D1[查询逻辑 10行] D -.传入Lambda.-> D2[转换逻辑 5行] E -.传入Lambda.-> E1[查询逻辑 10行] E -.传入Lambda.-> E2[转换逻辑 5行] F -.传入Lambda.-> F1[查询逻辑 10行] F -.传入Lambda.-> F2[转换逻辑 5行] style D fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style E fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style F fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff style G fill:#339af0,stroke:#1c7ed6,stroke-width:3px,color:#fff style G1 fill:#74c0fc style G2 fill:#74c0fc style G3 fill:#a5d8ff style G4 fill:#a5d8ff style G5 fill:#a5d8ff style G6 fill:#a5d8ff style D1 fill:#b2f2bb style D2 fill:#b2f2bb style E1 fill:#b2f2bb style E2 fill:#b2f2bb style F1 fill:#b2f2bb style F2 fill:#b2f2bb
优势一目了然:
- 通用逻辑封装在基类(150行,所有策略共享)
- 每个策略只写业务逻辑(30行)
- 修改通用逻辑 = 只改基类
- 新增策略 = 继承基类 + 2个Lambda
核心代码实现
1. 泛型抽象基类
java
public abstract class BaseExportStrategy implements ExportStrategy {
// 泛型函数式接口:分页查询回调
@FunctionalInterface
public interface PageQueryCallback<T> {
List<T> query(int pageNo, int pageSize);
}
// 泛型函数式接口:数据转换回调
@FunctionalInterface
public interface DataConvertCallback<T, R> {
List<R> convert(List<T> dataList); // T → R 转换
}
/**
* 双泛型方法:分页查询并流式导出
*
* @param queryCallback 查询回调(返回类型T)
* @param convertCallback 转换回调(T → R)
* @param param 导出参数
* @param voClass 导出VO类型
* @param sheetName Excel表名
*/
protected <T, R> ExportResult exportDataWithPagingAndZip(
PageQueryCallback<T> queryCallback, // T: 查询结果类型
DataConvertCallback<T, R> convertCallback, // R: 导出VO类型
CreateExportTaskParam param,
Class<?> voClass,
String sheetName
) {
int currentPage = 1;
int fileIndex = 1;
int totalCount = 0;
List<R> currentFileData = new ArrayList<>();
String tmpDir = createTempDirectory(param);
try {
while (true) {
// 1. 调用Lambda:查询一页数据
List<T> pageData = queryCallback.query(currentPage, 5000);
if (pageData == null || pageData.isEmpty()) break;
// 2. 调用Lambda:转换数据(T → R)
List<R> convertedData = convertCallback.convert(pageData);
currentFileData.addAll(convertedData);
totalCount += convertedData.size();
// 3. 达到阈值(50000行),写入Excel
if (currentFileData.size() >= 50000) {
writeExcelFile(tmpDir, getExportType(), fileIndex, currentFileData, voClass, sheetName);
fileIndex++;
currentFileData = new ArrayList<>();
}
currentPage++;
}
// 4. 写入剩余数据
if (!currentFileData.isEmpty()) {
writeExcelFile(tmpDir, getExportType(), fileIndex, currentFileData, voClass, sheetName);
}
// 5. 压缩ZIP + 上传OSS(通用逻辑)
String zipFilePath = zipFiles(tmpDir, param);
String zipFileName = FileUtil.getName(zipFilePath);
String fileUrl = uploadZipFileToOSS(zipFilePath, zipFileName);
return new ExportResult(fileUrl, totalCount, zipFileName);
} catch (Exception e) {
LogUtil.error(log, "BaseExportStrategy.exportDataWithPagingAndZip >> 流式导出失败", e);
throw MemberException.INVALID_PARAM_ERROR.newInstance("流式导出失败");
} finally {
deleteTempDirectory(tmpDir); // 清理临时文件
}
}
// 通用方法:写入Excel文件
private <R> void writeExcelFile(String tmpDir, String baseFileName, int fileIndex,
List<R> data, Class<?> voClass, String sheetName) {
String fileName = String.format("%s_%d.xlsx", baseFileName, fileIndex);
String filePath = tmpDir + File.separator + fileName;
EasyExcel.write(filePath, voClass).sheet(sheetName).doWrite(data);
}
// 通用方法:压缩为ZIP
private String zipFiles(String tmpDir, CreateExportTaskParam param) {
String zipFileName = generateZipFileName(param);
File tmpDirFile = new File(tmpDir);
String zipFilePath = tmpDirFile.getParent() + File.separator + zipFileName;
ZipUtil.zip(tmpDir, zipFilePath, true);
return zipFilePath;
}
// 通用方法:上传OSS
private String uploadZipFileToOSS(String zipFilePath, String zipFileName) {
byte[] zipFileBytes = FileUtil.readBytes(zipFilePath);
return ExportUtils.uploadExportFileToOSS(zipFileBytes, zipFileName, sysConfig);
}
// 子类实现:定义导出类型(用于生成文件名)
protected abstract String getExportType();
}
2. 具体策略实现(只需30行!)
java
@Component("StoreConsumptionRecordStrategy")
public class StoreConsumptionRecordStrategy extends BaseExportStrategy {
@Resource
private MemberStatisticsService memberStatisticsService;
@Resource
private StoreConsumptionRecordMapper storeConsumptionRecordMapper;
@Override
public void exportData(CreateExportTaskParam param) {
FindConsumeBillListParam queryParam = parseBusinessParams(param);
// 核心代码:只写业务逻辑
ExportResult result = exportDataWithPagingAndZip(
// Lambda:分页查询逻辑(10行)
(pageNo, pageSize) -> {
PageParam<FindConsumeBillListParam> pageParam = new PageParam<>();
pageParam.setQuery(queryParam);
pageParam.setPage(pageNo);
pageParam.setPageSize(pageSize);
return memberStatisticsService.findConsumeBillList(pageParam).getList();
},
// 方法引用:数据转换逻辑(DTO → VO)
this::convertToExportVO,
param,
StoreConsumptionRecordExportVO.class,
"门店消费记录"
);
updateExportRecord(param, result);
}
@Override
protected String getExportType() {
return "门店消费记录";
}
// 唯一需要自己实现的业务逻辑(5行)
private List<StoreConsumptionRecordExportVO> convertToExportVO(
List<FindConsumeBillDetailResult> consumeBillList
) {
return storeConsumptionRecordMapper.toStoreConsumptionRecordExportVOList(consumeBillList);
}
}
新增充值记录导出?同样30行:
java
@Component("StoreRechargeRecordStrategy")
public class StoreRechargeRecordStrategy extends BaseExportStrategy {
@Override
public void exportData(CreateExportTaskParam param) {
ExportResult result = exportDataWithPagingAndZip(
// Lambda:查询充值记录
(pageNo, pageSize) -> queryRechargeRecords(pageNo, pageSize),
// Lambda:转换VO
this::convertToExportVO,
param,
RechargeRecordExportVO.class,
"充值记录"
);
updateExportRecord(param, result);
}
@Override
protected String getExportType() {
return "充值记录";
}
}
对比:导出模块 vs 导入模块
代码量对比
维度 | 导入模块(无泛型) | 导出模块(泛型+函数式) | 减少比例 |
---|---|---|---|
基类代码 | 0行(无基类) | 150行(一次性投入) | - |
单个策略 | 215行 | 35行 | ↓ 84% |
3个策略总计 | 645行 | 105行 + 150行基类 = 255行 | ↓ 60% |
新增策略成本 | 200行 | 30行 | ↓ 85% |
核心差异
对比项 | 导入模块 | 导出模块 |
---|---|---|
类型安全 | 字符串key + 强制转换 | 泛型编译期检查 |
代码复用 | 每个策略200行重复代码 | 基类封装通用逻辑 |
扩展性 | 复制粘贴 | 继承基类+2个Lambda |
可维护性 | 改一处改三处 | 改基类即可 |
可读性 | 硬编码 + 重复代码 | 业务逻辑清晰 |
核心价值:泛型+函数式的三大杀器
1. 类型安全:编译期就报错
导入模块(运行时炸弹):
ini
// 字符串key拼错,运行时才发现
String storeId = jsonObject.getString("stroeId"); // 少个字母!运行时NPE
// 类型转换错误,运行时ClassCastException
List<CustomerImportParam> list = (List<CustomerImportParam>) fileExcelService.getMemberInfoByExcel(...);
导出模块(编译期检查):
kotlin
// 泛型方法:类型明确
ExportResult result = exportDataWithPagingAndZip(
(pageNo, pageSize) -> queryConsumeBills(pageNo, pageSize), // 返回 List<FindConsumeBillDetailResult>
this::convertToExportVO, // 接受 List<FindConsumeBillDetailResult>
param,
StoreConsumptionRecordExportVO.class,
"门店消费记录"
);
// 如果类型不匹配,编译器立即报错!
2. 代码复用:一套基类,适用所有策略
导入模块(复制粘贴):
typescript
// 会员导入:200行
@Component("MemberImportStrategy")
public class MemberImportStrategy implements ImportStrategy {
public void importData(ImportParam param) {
// 解析Excel(20行)
// 解析参数(15行)
// 构建对象(25行)
// 执行导入(10行)
// 处理失败(15行)
// 上传OSS(50行)
// 异常处理(15行)
}
}
// 加油会员导入:复制粘贴200行,改几个类名
@Component("JiayouMemberImportStrategy")
public class JiayouMemberImportStrategy implements ImportStrategy {
public void importData(ImportParam param) {
// 完全相同的代码...
}
}
导出模块(泛型复用):
kotlin
// 基类:一次性写150行(所有策略共享)
public abstract class BaseExportStrategy<T, R> {
protected <T, R> ExportResult exportDataWithPagingAndZip(
PageQueryCallback<T> queryCallback,
DataConvertCallback<T, R> convertCallback,
CreateExportTaskParam param,
Class<?> voClass,
String sheetName
) {
// 分页查询(10行)- 适用所有策略
// 数据转换(10行)- 适用所有策略
// Excel生成(20行)- 适用所有策略
// ZIP压缩(30行)- 适用所有策略
// OSS上传(20行)- 适用所有策略
// 异常处理(10行)- 适用所有策略
}
}
// 消费记录导出:只写30行业务逻辑
@Component("StoreConsumptionRecordStrategy")
public class StoreConsumptionRecordStrategy extends BaseExportStrategy {
public void exportData(CreateExportTaskParam param) {
ExportResult result = exportDataWithPagingAndZip(
this::queryData, // 查询逻辑
this::convertToVO, // 转换逻辑
param, VO.class, "sheet"
);
}
}
// 充值记录导出:同样30行
@Component("StoreRechargeRecordStrategy")
public class StoreRechargeRecordStrategy extends BaseExportStrategy {
public void exportData(CreateExportTaskParam param) {
ExportResult result = exportDataWithPagingAndZip(
this::queryData, this::convertToVO, param, VO.class, "sheet"
);
}
}
3. 函数式:灵活定制业务逻辑
导入模块(硬编码):
java
// 每个策略都要重写整个方法
public class MemberImportStrategy implements ImportStrategy {
public void importData(ImportParam param) {
// 硬编码:数据校验逻辑
for (CustomerImportParam item : memberList) {
if (StringUtils.isBlank(item.getPhone())) {
failList.add(new CustomerImportResult("手机号为空"));
}
}
// 硬编码:导入逻辑
MemberCustomerImportVerifyResult result = memberImportService.importCustomerList(...);
}
}
导出模块(Lambda灵活定制):
kotlin
// 基类提供泛型方法框架
public abstract class BaseExportStrategy {
protected <T, R> ExportResult exportDataWithPagingAndZip(
PageQueryCallback<T> queryCallback, // 函数式接口:灵活传入查询逻辑
DataConvertCallback<T, R> convertCallback, // 函数式接口:灵活传入转换逻辑
...
) {
List<T> pageData = queryCallback.query(currentPage, pageSize); // 调用Lambda
List<R> convertedData = convertCallback.convert(pageData); // 调用Lambda
}
}
// 具体策略:Lambda传入业务逻辑
@Component("StoreConsumptionRecordStrategy")
public class StoreConsumptionRecordStrategy extends BaseExportStrategy {
public void exportData(CreateExportTaskParam param) {
ExportResult result = exportDataWithPagingAndZip(
// Lambda:查询逻辑
(pageNo, pageSize) -> memberStatisticsService.findConsumeBillList(...).getList(),
// 方法引用:转换逻辑
this::convertToExportVO,
param, VO.class, "sheet"
);
}
}
还有什么场景可以用泛型+函数式?
场景1:多种支付方式路由
痛点: 微信支付、支付宝支付、银行卡支付,每种都要重复写订单创建、回调处理、退款逻辑。
解决方案:
java
// 泛型基类
public abstract class BasePaymentStrategy<T extends PaymentRequest, R extends PaymentResponse> {
protected R processPayment(
T request,
PaymentCallback<T, R> paymentCallback
) {
// 1. 参数校验(通用)
validateRequest(request);
// 2. 创建订单(通用)
PaymentOrder order = createOrder(request);
// 3. 调用Lambda:执行支付
R response = paymentCallback.pay(request);
// 4. 更新订单(通用)
updateOrder(order, response);
return response;
}
}
// 微信支付:只写30行
@Component("WechatPaymentStrategy")
public class WechatPaymentStrategy extends BasePaymentStrategy<WechatPayRequest, WechatPayResponse> {
public WechatPayResponse pay(WechatPayRequest request) {
return processPayment(
request,
// Lambda:调用微信支付API
req -> wechatPayClient.unifiedOrder(req)
);
}
}
// 支付宝支付:同样30行
@Component("AlipayPaymentStrategy")
public class AlipayPaymentStrategy extends BasePaymentStrategy<AlipayPayRequest, AlipayPayResponse> {
public AlipayPayResponse pay(AlipayPayRequest request) {
return processPayment(
request,
// Lambda:调用支付宝API
req -> alipayClient.tradePay(req)
);
}
}
场景2:多种消息推送渠道
痛点: 短信、邮件、站内信、企业微信,每种都要重复写模板渲染、发送逻辑、失败重试。
解决方案:
java
// 泛型基类
public abstract class BaseMessageStrategy<T extends MessageContent> {
protected void sendMessage(
T content,
MessageSendCallback<T> sendCallback
) {
// 1. 渲染模板(通用)
String renderedContent = renderTemplate(content);
// 2. 调用Lambda:执行发送
SendResult result = sendCallback.send(content, renderedContent);
// 3. 失败重试(通用)
if (!result.isSuccess()) {
retryService.addRetryTask(content);
}
// 4. 记录日志(通用)
messageLogService.log(content, result);
}
}
// 短信推送:只写核心逻辑
@Component("SmsMessageStrategy")
public class SmsMessageStrategy extends BaseMessageStrategy<SmsContent> {
public void send(SmsContent content) {
sendMessage(
content,
// Lambda:调用短信API
(smsContent, renderedContent) -> smsClient.send(smsContent.getPhone(), renderedContent)
);
}
}
// 邮件推送:同样简洁
@Component("EmailMessageStrategy")
public class EmailMessageStrategy extends BaseMessageStrategy<EmailContent> {
public void send(EmailContent content) {
sendMessage(
content,
// Lambda:调用邮件API
(emailContent, renderedContent) -> mailClient.send(emailContent.getEmail(), renderedContent)
);
}
}
泛型使用的一些问题(踩过才知道)
坑1:泛型擦除导致类型信息丢失
问题:
csharp
// 想通过反射创建泛型实例,但运行时泛型信息被擦除
public abstract class BaseStrategy<T> {
public T createInstance() {
// 编译错误!运行时不知道T是什么类型
return new T();
}
}
解决方案:传入Class对象
csharp
public abstract class BaseStrategy<T> {
protected T createInstance(Class<T> clazz) {
try {
// 通过Class对象创建实例
return clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("创建实例失败", e);
}
}
}
// 使用
@Component("MemberStrategy")
public class MemberStrategy extends BaseStrategy<MemberVO> {
public MemberVO create() {
return createInstance(MemberVO.class); // 传入Class对象
}
}
坑2:泛型与数组混用
问题:
arduino
// 编译错误!不能创建泛型数组
public <T> T[] createArray(int size) {
return new T[size];
}
// 编译错误!不能创建泛型集合数组
List<String>[] stringLists = new List<String>[10];
解决方案:用List替代数组
arduino
// 使用List
public <T> List<T> createList(int size) {
return new ArrayList<>(size);
}
// 使用List<List<T>>
List<List<String>> stringLists = new ArrayList<>();
坑3:忘记使用通配符导致不灵活
问题:
scss
// 不够灵活:只能接受 List<Number>
public void process(List<Number> numbers) {
for (Number num : numbers) {
System.out.println(num.doubleValue());
}
}
// 编译错误!List<Integer> 不是 List<Number>
List<Integer> integers = Arrays.asList(1, 2, 3);
process(integers);
解决方案:使用上界通配符
scss
// 灵活:接受 Number 及其子类
public void process(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num.doubleValue());
}
}
// 可以传入 List<Integer>
List<Integer> integers = Arrays.asList(1, 2, 3);
process(integers);
// 可以传入 List<Double>
List<Double> doubles = Arrays.asList(1.5, 2.5);
process(doubles);
总结
- 别等代码烂到不能维护才重构
- 别怕泛型语法复杂,写一次就会了
- 别舍不得150行基类代码,它能让你省下3000行重复代码
下次遇到策略模式,问自己3个问题:
- 这些策略有没有相似的流程?(有 → 抽象基类)
- 这些策略处理的数据类型不同吗?(是 → 泛型)
- 这些策略的业务逻辑需要灵活定制吗?(需要 → 函数式接口)
再加上现在ai进化的飞快,很多时候我们更需要有这样的抽取设计的思想。只要有了剩下的ai都会完成。