模板方法模式(Template Method Pattern)

模板方法模式用于定义算法骨架,将不同步骤留给子类实现。在单据审批功能中,如请假、加班申请,虽然审批逻辑相同,但同意或拒绝后的操作不同。通过模板方法,可以避免在每个模块的service中重复实现,提高代码可维护性。Controller通过简单工厂方法调用相应service,减少重复代码,便于逻辑变更时统一修改。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板方法模式(Template)

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现
模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

业务场景

假设我们现在要实现一个对单据审批功能,有请假申请、加班申请。他们的算法逻辑都一样,只有同意或拒绝后的操作不一样。你先不要为什么是这些功能,你只要知道我们现在有这么多的申请功能,然后有这么多申请的逻辑需要你实现。怎么做,是分别再各个模块的service里面实现一边吗?
注意,他们有一样的算法逻辑。接下来看怎么实现。其实实现起来很简单。

/**
 * 工作流处理类:模板方法
 */
public abstract class WorkFlowService{
    /**
     * 审批单据工作流:同意/拒绝
     *
     * @param tenantId      租户id
     * @param id            单据主键
     * @param node          流程节点:最后节点/流程中节点
     * @param approveResult 审批结果:同意/拒绝
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void workFlowChangeStatus(Long tenantId, Long id, String node, String approveResult) {
        // 判断申请记录是否存在
        check(tenantId, id);

        // 最后节点
        if (ApproveResult.END_NODE.equals(node)) {
            // 同意
            if (ApproveResult.APPROVE.value().equals(approveResult)) {
                approve(tenantId, id);
            }
            // 拒绝
            if (ApproveResult.REJECT.value().equals(approveResult)) {
                reject(tenantId, id);
            }
        }

        // 流程中间节点
        if (ApproveResult.PROCESS_NODE.equals(node)) {
            // 中间节点:拒绝
            if (ApproveResult.REJECT.value().equals(approveResult)) {
                reject(tenantId, id);
            }
        }
    }
    
    /**
     * 校验
     */
    protected abstract void check(Long tenantId, Long id);

    /**
     * 拒绝操作
     */
    protected abstract void reject(Long tenantId, Long id);

    /**
     * 同意操作
     */
    protected abstract void approve(Long tenantId, Long id);
}

check reject approve是三个勾子方法,也就是需要用户自定以的逻辑实现方法,而workFlowChangeStatus就是模板方法了,我们可以看到里面的逻辑不跟任何一个service相依赖,是通用的。

/**
 * 请假申请
 */
@Service
public class LeaveApplyService extends WorkFlowService {
    @Override
    protected void reject(Long tenantId, Long id) {
        // todo 拒绝请假逻辑
    }
    
    @Override
    protected void approve(Long tenantId, Long id) {
        // todo 同意请假逻辑
    }
}
/**
 * 加班申请
 */
@Service
public class OvertimeApplyService extends WorkFlowService {
    @Override
    protected void reject(Long tenantId, Long id) {
        // todo 拒绝加班逻辑
    }
    
    @Override
    protected void approve(Long tenantId, Long id) {
        // todo 同意加班逻辑
    }
}

上面我们是不是已经写好了Service相关实现,也就是模板方法的基本思想的实现,也就是通过继承来实现的。

接下来是Controller。那我们是不是要每个Service方法都写一个对应的Controller,再写一遍对应的接口。我们有更简便的实现,既然每个service都继承了模板方法,那我们可以写一个通用的Controller,通过传入不同的类型来调用不同service,这里使用简单工厂方法的思想来实现。

/**
 * 工作流入口
 */
@RestController
@RequestMapping("/work-flow")
public class ContractWorkFlowController {

    @Autowired
    private WorkFlowDispatcher workFlowDispatcher;

    /**
     * 工作流审批调用接口,根据操作类型 optional,执行不同操作{@link WorkFlowOptionalType}
     *
     * @param tenantId 租户id
     * @param optional 操作类型
     * @param params   工作流的流程变量
     */
    @Permission(level = ResourceLevel.ORGANIZATION)
    @ApiOperation(value = "工作流审批操作接口")
    @GetMapping(value = "/workflow/{optional}")
    public void workFlowOptional(@PathVariable("organizationId") Long tenantId,
                                 @PathVariable("optional") String optional,
                                 WorkFlowParams params) {
        params.setTenantId(tenantId);
        workFlowDispatcher.invokeService(optional, params);
    }
}

workFlowDispatcher这个类就是简单工厂的实现,根据单据类型documentType来决定调用哪个service, 根据操作类型optional来选择审批操作(流程中/流程结果)

/**
 * 工作流分发
 */
@Service
public class WorkFlowDispatcher {
    @Autowired
    private LeaveApplyService leaveApplyService;
    @Autowired
    private OvertimeApplyService overtimeApplyService;

    /**
     * 调用service,执行审批操作
     *
     * @param optional 审批操作类型{@link WorkFlowOptionalType}
     * @param params
     */
    public void invokeService(String optional, WorkFlowParams params) {
        Long tenantId = params.getTenantId();
        Long documentId = params.getDocumentId();
        String documentType = params.getDocumentType();
        String approveResult = params.getApproveResult();
		
        IWorkFlowService workFlowService = chooseService(documentType);
        switch (optional) {
            // 工作流:审批结果
            case WorkFlowOptionalType.RESULT:
                workFlowService.workFlowChangeStatus(tenantId, documentId, ApproveResult.END_NODE, approveResult);
                break;
            // 工作流:审批中节点
            case WorkFlowOptionalType.PROCESS:
                workFlowService.workFlowChangeStatus(tenantId, documentId, ApproveResult.PROCESS_NODE, approveResult);
                break;
            default:
                throw new IllegalArgumentException("error.workFlow.optional.illegal");
        }
    }

    /**
     * 根据单据类型,选择对应的service
     *
     * @param documentType {@link WorkFlowDocumentTypeEnum}
     * @return
     */
    private IWorkFlowService chooseService(String documentType) {
        if (WorkFlowDocumentTypeEnum.LEAVEAPPLY.value().equals(documentType)) {
            return leaveApplyService;
        } else if (WorkFlowDocumentTypeEnum.OVERTIMEAPPLY.value().equals(documentType)) {
            return overtimeApplyService;
        }  else {
            throw new IllegalArgumentException("error.workFlow.documentType.illegal");
        }
    }
}

这样做有什么用呢,直接写几个Controller也能搞定。这样写能提高代码的可维护性,当逻辑发生修改时,只需要改这一个类。如果你每个service都重复实现一个对应的Controller,如果要变更,每个地方都需要改。你再想想哪种实现会更好,并且,你写那么多Controller都是再做同样的事情,你不觉得是没有必要的重复吗。

本期分享就到这里了,希望你能再能够将设计模式在实际开发中有所运用。如果有疑惑、或者对文章有什么建议欢迎留言交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值