组合还是继承?我在项目里主要看这两个判断

组合优先于继承这个原则没错,但也只是一种建议,而不是绝对的。

很多人看到两段代码差不多,第一反应就是抽个父类。它提醒的就是这件事。降低耦合、避免破坏封装、规避脆弱基类问题,这些都是组合的优势。但实际项目里有一种场景,继承反而是更自然的工程选择:

就是业务存在一条固定流程,只允许局部步骤变化。

我之前上线的钉钉OA工单审批就是这么个场景。核心操作就三个:创建OA工单、取消OA工单、钉钉回调处理。三个操作的流程是固定的。公司OA工单类型很多,补货订货审批、门店营业时间变更审批、商品售罄审批等一共6种,以后还会更多。流程固定,每种工单只有表单内容和审批后的处理不同,类型还很多,用继承是自然选择。

有人可能会说,为什么不用组合?把对接OA工单的逻辑拆成校验器、表单组装器、创建器等几个独立组件,每种工单类型组合这些组件,再自己组织调用顺序,不也能实现吗。

其实我一开始也这么想过。能实现。但流程对不对,更多得靠每个实现类自己盯着。每个实现类仍然拥有重新排列步骤的自由,有人可能忘了校验,有人可能先组装表单再组装基本信息。这些错误在代码审查时不一定能发现,上了生产就是事故。

我的做法是直接在父类把流程锁死。看一下AbstractDingTalkWorkFlowStrategy里createWorkFlow方法的核心流程:

Java 复制代码
// 校验 → 组装基本信息 → 组装表单 → 创建
this.checkCanCreateWorkFlow(createWorkFlowCommand.getBizId());
CreateWorkFlowDTO dto = this.assembledBaseInfo(createWorkFlowCommand);
this.assembledFormComponents(dto);
this.doCreateWorkFlow(dto);

校验、组装基本信息、组装表单、创建,四步顺序写死在父类里。子类碰不到流程,只能实现变化点。

子类这边就简单了,只剩两件事。父类声明了两个抽象方法:

Java 复制代码
// 子类必须实现的两个变化点
protected abstract void assembledFormComponents(CreateWorkFlowDTO createWorkFlowDTO);
protected abstract void approvalCompleted(DingTalkWorkFlowRecordEntity entity);

子类必须实现这两个方法,也只能实现这两个方法。补货订货的子类ReplenishmentOrderWorkFlowStrategy,在assembledFormComponents里组装补货订货的表单数据,在approvalCompleted里审批通过后发钉钉消息通知相关人员。

门店营业时间变更的子类ShopTimeChangeApplyWorkFlowStrategy,组装的是门店时间变更的表单,审批通过后回调门店系统更新状态。每个子类只关心自己那部分差异,cancel、handleCallBack这些固定逻辑在父类写一次,6个子类共享,不用复制。

还有一个细节。checkCanCreateWorkFlow这个方法在父类里是空的,补货订货的子类重写了它,检查是否重复申请、是否超过次数限制。不需要校验的子类什么都不用做。需要的人重写,不需要的人不管。扩展点固定在流程里,不会因为忘记调用而出问题。

至于组合。你要组织的是一组独立能力,各管各的,组合更合适。支付渠道,支付宝、微信、银联,每个都是独立能力,可以组合进任何业务。消息通知,钉钉、短信、邮件,也是独立能力。组合让每个能力独立变化、自由拼装,不需要绑死在一条流程里。

不过,继承让我坚持这么用的,还不是这些。

流程一旦变化,所有子类应该一起变化。比如审批流程以后统一加一步权限校验,在父类的createWorkFlow里加一行checkPermission(),6个审批类型自动生效,不用逐个改。你想想,换成组合的话,每个实现类都得自己加这一步,漏掉一个就是线上漏洞。继承共享的不只是今天的代码,更是未来的修改。值得继承的,是未来应该一起演进的流程。

组合优先于继承没有错。它批评的是把继承当作默认复用机制,为了少写几行代码就建继承关系。问题从来不在继承,在于你为什么建立这层继承。

判断什么时候用组合、什么时候用继承,我一般就看两件事:你面对的是几个独立能力,还是一条稳定流程里的几个变化点?这段逻辑未来是各自独立变化,还是应该随着所有实现一起演进?组合负责组织彼此独立的能力,继承负责约束需要一起演进的流程。如果一段逻辑未来应该随着所有实现一起变化,继承往往比组合更自然。面试遇到这个问题,能从业务场景和未来演进的角度给判断.

学过设计模式的话应该看出来了,上面的写法就是模板方法模式。设计模式只是后来的人给这种思想起了名字。业务怎么变,代码就该怎么设计。

相关推荐
niuju1 小时前
一种基于 Agent 的需求规范化闭环实践:让合规与规范内建于流程
后端
wear工程师1 小时前
setState 到底是不是异步的?这题从 React 16 问到 18,标准答案早就变了
react.js·面试
团团崽_七分甜1 小时前
四大数据库底层原理详解
后端
glartchen1 小时前
使用 Pixi 配置 Whisper 环境,实现采用本地大模型读取视频字幕
后端
站大爷IP1 小时前
明明传了参数,进程池却说没找到?记一次变量丢失的排查实录
后端
momo1 小时前
JAVA基础知识
java·开发语言
就叫_这个吧2 小时前
Activiti7工作流入门+案例应用
java·activiti7·工作流
Z_W_H_2 小时前
minikube start --driver=docker --memory=4000mb出现PROVIDER_DOCKER_NEWGRP
java·docker·minikube
用户6757049885022 小时前
从失真到绝对精准:Go语言中如何进行高精度计算?
后端·go