泛型+函数式:让策略模式不再是复制粘贴地狱

泛型+函数式:让策略模式不再是复制粘贴地狱

从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

目前已有的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行

实践:用泛型+函数式设计导出模块

后来要做导出功能,吸取了导入模块的教训,这次用泛型+函数式重新设计。

设计思路

核心目标:规范实现类的行为,策略实现类只需要关注自己的业务流程

  1. 泛型抽象基类 BaseExportStrategy

    • 封装通用流程:分页查询、Excel生成、ZIP压缩、OSS上传、异常处理
    • 使用泛型方法适配不同数据类型
  2. 泛型函数式接口

    • PageQueryCallback<T>:定义查询逻辑
    • DataConvertCallback<T, R>:定义转换逻辑(T → R)
  3. 具体策略只需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

优势一目了然:

  • 通用逻辑封装在基类(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个问题:

  1. 这些策略有没有相似的流程?(有 → 抽象基类)
  2. 这些策略处理的数据类型不同吗?(是 → 泛型)
  3. 这些策略的业务逻辑需要灵活定制吗?(需要 → 函数式接口)

再加上现在ai进化的飞快,很多时候我们更需要有这样的抽取设计的思想。只要有了剩下的ai都会完成。

相关推荐
captain3763 小时前
Java线性表
java·开发语言
tuokuac3 小时前
Java String类中的lastIndexOf方法的应用场景
java·开发语言
柑木3 小时前
开发必备-使用DevContainer技术消除 “在我这能运行”
后端
怪兽20143 小时前
Looper、MessageQueue、Message及Handler的关系是什么?如何保证MessageQueue的并发访问安全?
android·面试
武子康3 小时前
大数据-122 - Flink Watermark 全面解析:事件时间窗口、乱序处理与迟到数据完整指南
大数据·后端·flink
weixin_379880923 小时前
.Net Core WebApi集成Swagger
java·服务器·.netcore
@逆风微笑代码狗3 小时前
147.《手写实现 Promise.all 与 Promise.race》
java
她说彩礼65万3 小时前
Asp.net core Kestrel服务器详解
服务器·后端·asp.net
3 小时前
JUC专题-线程安全性之可见性有序性
后端