策略模式的两种实现:抽象类和接口

抽象类(策略模式 + 模板方法模式)代码实现

社保公积金上传:社保、公积金、雇员、自有员工:一共四个excel模板,都需要上传的文件服务器,解析方法不一样

1、抽象父类定义

java 复制代码
@Slf4j
public abstract class PaySipfUploadService {
    
    // ✅ 共享字段
    @Resource
    private FileAttachmentApi fileAttachmentApi;
    
    // ✅ 抽象方法(子类实现)
    abstract SipfFeeItemEnum getSipfFeeItem();
    abstract PersTypeEnum getPersType();
    abstract String getAttachType();
    
    // ✅ 模板方法(固定流程)
    public List<PaySipfDtlTmpDO> uploadHandle(MultipartFile file, PaySipfDtlTmpUploadVO tmpUploadResult) {
        // 步骤1:调用子类解析
        List<PaySipfDtlTmpDO> sipfDtlTmps = upload(file, tmpUploadResult);
        
        // 步骤2:判断是否成功
        if (YesOrNo.YES.getCode().equals(tmpUploadResult.getFlag())) {
            // 步骤3:上传文件到 XSKY(共用逻辑)
            AttachmentRespDTO upload = fileAttachmentApi.upload(
                AttachmentReqDTO.builder()
                    .file(file)
                    .attachType(getAttachType())  // 子类提供
                    .isSave(YesOrNo.YES.getCode())
                    .isDelete(YesOrNo.YES.getCode())
                    .build()
            );
            
            if (null == upload || null == upload.getFileId()) {
                log.error("上传XSKY失败");
                throw exception(PayErrorCodeConstants.PAY_UPLOAD_ERROR);
            }
            tmpUploadResult.setFileId(upload.getFileId());
        }
        
        return sipfDtlTmps;
    }
    
    // ✅ 抽象方法(子类实现具体解析)
    abstract List<PaySipfDtlTmpDO> upload(MultipartFile file, PaySipfDtlTmpUploadVO tmpUploadResult);
}

2、策略注册与选择

java 复制代码
@Service
public class PaySipfApplServiceImpl {
    
    @Resource
    private List<PaySipfUploadService> sipfUploadServices;  // Spring 自动注入所有子类
    
    private Map<String, PaySipfUploadService> sipfUploadServiceMap;
    
    @PostConstruct
    private void init() {
        // 手动构建策略映射表
        sipfUploadServiceMap = sipfUploadServices.stream()
            .collect(Collectors.toMap(
                s -> s.getSipfFeeItem().getKey() + s.getPersType().getKey(),  // 业务组合 Key
                Function.identity()
            ));
    }
    
    public PaySipfDtlTmpUploadVO uploadDetail(PaySipfDtlTmpUploadVO reqVO) {
        String key = reqVO.getSipfType() + reqVO.getPersType();  // "SOCIALEMP"
        
        // 根据业务类型选择策略
        PaySipfUploadService uploadService = sipfUploadServiceMap.get(key);
        Assert.notNull(uploadService, "服务不能为空!");
        
        // 执行模板方法
        return uploadService.uploadHandle(reqVO.getFile(), tmpUploadResult);
    }
}

3、具体策略实现

java 复制代码
@Service
public class SocialEmpUploadService extends PaySipfUploadService {
    
    @Override
    SipfFeeItemEnum getSipfFeeItem() {
        return SipfFeeItemEnum.SOCIAL;
    }
    
    @Override
    PersTypeEnum getPersType() {
        return PersTypeEnum.EMP;
    }
    
    @Override
    String getAttachType() {
        return "SOCIAL_EMP";
    }
    
    @Override
    List<PaySipfDtlTmpDO> upload(MultipartFile file, PaySipfDtlTmpUploadVO result) {
        // 社保雇员 Excel 的特殊解析逻辑
        // ...
    }
}

接口(纯策略模式)代码实现

服务费计算有9种公式类型需要解析:如固定值、阶梯、成本比例等

1、接口定义

java 复制代码
public interface CalcFormulaService {
    /**
     * 计算公式
     * @param reqVO 需要计算的数据
     * @return 结果
     */
    FormulaDTO calcFormula(NeedCalculateData reqVO);
}

2、策略注册与选择

java 复制代码
@Service
public class SerIncomeDetailServiceImpl {
    
    // ✅ Spring 自动注入为 Map,Key 就是 @Service 的值
    @Resource
    private Map<String, CalcFormulaService> calcserviceMap;
    
    public void calculate(NeedCalculateData needCalculateData) {
        String formulaType = formulaInfo.getFormulaCalc().getCode();  // "A1", "A2", "BX" 等
        
        // ✅ 直接根据公式类型获取策略并执行
        FormulaDTO formulaDTO = calcserviceMap.get(formulaType).calcFormula(needCalculateData);
        
        // ...
    }
}

3、具体策略实现

java 复制代码
// 策略1:固定值计算
@Slf4j
@Service("A1")  // ← Bean 名称就是策略 Key
public class FormulaFixedValueServiceImpl implements CalcFormulaService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FormulaDTO calcFormula(NeedCalculateData reqVO) {
        log.info("A1 - 固定值计算");
        List<FormulaDetailDTO> formulaDetailList = reqVO.getFormulaDetail();
        FormulaDetailDTO formulaDetail = formulaDetailList.get(0);
        List<FormulaDetailFactorDTO> factors = formulaDetail.getFactors();
        FormulaDetailFactorDTO factor = factors.get(0);
        
        BigDecimal fixValue = factor.getFactorValue();
        
        FormulaDTO formulaDTO = new FormulaDTO();
        formulaDTO.setFixValue(fixValue);
        return formulaDTO;
    }
}

// 策略2:阶梯计算
@Slf4j
@Service("A2")
public class FormulaStepServiceImpl implements CalcFormulaService {
    
    @Resource
    private EmployeeApi employeeApi;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FormulaDTO calcFormula(NeedCalculateData reqVO) {
        log.info("A2 - 阶梯计算");
        // 查询在岗雇员数量
        EmpDutyNumberReqDTO reqDTO = new EmpDutyNumberReqDTO();
        reqDTO.setProjectId(reqVO.getProjectId());
        reqDTO.setQueryDate(reqVO.getBusinessDay());
        Long empDutyNumber = employeeApi.getEmpDutyNumber(reqDTO);
        
        // 根据人数匹配阶梯区间
        BigDecimal num = new BigDecimal(empDutyNumber != null ? empDutyNumber : 0);
        FormulaDetailDTO formulaDetail = reqVO.getFormulaDetail().stream()
            .filter(dto -> num.compareTo(dto.getMinValue()) >= 0 
                        && (dto.getMaxValue() == null || num.compareTo(dto.getMaxValue()) <= 0))
            .findFirst()
            .orElse(null);
        
        Assert.notNull(formulaDetail, "公式配置不正确!");
        
        BigDecimal fixValue = formulaDetail.getFactors().get(0).getFactorValue();
        
        FormulaDTO formulaDTO = new FormulaDTO();
        formulaDTO.setFixValue(fixValue);
        return formulaDTO;
    }
}

// 策略3:成本比例计算
@Slf4j
@Service("BX")
public class FormulaCostRatioServiceImpl implements CalcFormulaService {
    
    @Resource
    private Map<String, CalcAmountService> calcAmountServiceMap;  // ← 嵌套策略模式
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FormulaDTO calcFormula(NeedCalculateData reqVO) {
        log.info("BX - 成本比例计算");
        List<FormulaDetailDTO> formulaDetailList = reqVO.getFormulaDetail();
        
        List<BigDecimal> costBaseValueList = new ArrayList<>();
        BigDecimal costRatio = null;
        BigDecimal btmLineValue = null;
        
        FormulaDetailDTO formulaDetail = formulaDetailList.get(0);
        List<FormulaDetailFactorDTO> factors = formulaDetail.getFactors();
        
        for (FormulaDetailFactorDTO factor : factors) {
            String factorType = factor.getFactorType();
            if (FactorEnum.COST.getCode().equals(factorType)) {
                // 根据不同的成本基数类型计算金额(再次使用策略模式)
                List<CostBasesDTO> costBasesDTOList = factor.getCostBases();
                Set<String> typeList = new HashSet<>();
                // ... 分组逻辑
                
                for (String type : typeList) {
                    BigDecimal costBaseValue = calcAmountServiceMap.get(type).calcAmount(reqVO);
                    if (costBaseValue != null) {
                        costBaseValueList.add(costBaseValue);
                    }
                }
            } else if (FactorEnum.RATIO.getCode().equals(factorType)) {
                costRatio = factor.getFactorValue();
            } else if (FactorEnum.BTM_LINE.getCode().equals(factorType)) {
                btmLineValue = factor.getFactorValue();
            }
        }
        
        FormulaDTO formulaDTO = new FormulaDTO();
        formulaDTO.setCostBaseValue(costBaseValueList.toArray(new BigDecimal[0]));
        formulaDTO.setCostRatio(costRatio);
        formulaDTO.setBtmLineValue(btmLineValue);
        return formulaDTO;
    }
}

详细对比表

对比维度 方式一:抽象类(策略+模板方法) 方式二:接口(纯策略)
核心类型 abstract class interface
设计模式 策略模式 + 模板方法模式 纯策略模式
依赖注入 List< PaySipfUploadService> Map<String, CalcFormulaService>
Key 生成 手动构建(@PostConstruct) Spring 自动(@Service 名称)
Key 来源 业务组合:sipfFeeItem + persType Bean 名称:"A1", "A2", "BX"
代码复用 ✅ 高(共享字段和方法) ❌ 无(各实现完全独立)
流程控制 ✅ 父类控制(模板方法) ❌ 各自控制
共享状态 ✅ 可以(如 fileAttachmentApi) ❌ 不可以
继承关系 单继承(extends) 多实现(implements)
扩展性 ✅ 优秀(开闭原则) ✅ 优秀(开闭原则)
适用场景 有共同流程和逻辑 只有行为契约,无共同逻辑
复杂度 中等(有继承层次) 简单(扁平结构)
维护成本 中等(修改父类影响所有子类) 低(各实现互不影响)

PaySipfUploadService(抽象类) ✅ 正确选择

原因:所有上传服务都有共同流程(解析 → 校验 → 上传文件)

CalcFormulaService(接口) ✅ 正确选择

原因:各公式计算逻辑完全不同,没有可复用代码

优缺点对比

类型 优点 缺点 适用场景
接口(纯策略模式) 代码复用率高、流程可控、共享资源方便、符合 DRY 原则 耦合度高、继承层次复杂、测试复杂、灵活性差、需要手动初始化 Map 有共同流程和逻辑、需要共享资源、想控制执行流程、业务类型相对稳定
抽象类(策略模式 + 模板方法模式) 解耦彻底、灵活性高、易于测试、Spring 自动管理、符合单一职责原则、支持嵌套策略 代码可能重复、无法共享状态、缺乏流程控制、可能出现 Key 冲突 各实现完全独立、策略类型很多且经常新增、需要多重实现、执行流程差异大
相关推荐
液态不合群1 小时前
Redis--哨兵机制与CAP定理
java·redis·bootstrap
曹牧1 小时前
Java:PDF文件扁平化处理
java·开发语言·pdf
灰色人生qwer1 小时前
解决IDEA运行Java程序jdk版本不匹配问题
java·开发语言·intellij-idea
yaoxin5211231 小时前
405. Java 文件操作基础 - 装饰者模式与 I/O Streams
java·开发语言·python
xiufeia1 小时前
后端项目初始化的一些小坑点
java·junit·maven·idea
丑八怪大丑1 小时前
JDBC基础篇
java·sql
环流_2 小时前
Redis:epoll和IO多路复用
java·redis·mybatis
Chase_______2 小时前
Java基础语言 ④ :面向对象核心——构造方法、this关键字与对象内存模型详解
java·开发语言·面向对象·类与对象
欢璃2 小时前
表白墙案例
java·开发语言·jvm·spring boot·spring·maven·mybatis