组合还是继承?我在项目里主要看这两个判断
组合优先于继承这个原则没错但也只是一种建议而不是绝对的。很多人看到两段代码差不多第一反应就是抽个父类。它提醒的就是这件事。降低耦合、避免破坏封装、规避脆弱基类问题这些都是组合的优势。但实际项目里有一种场景继承反而是更自然的工程选择就是业务存在一条固定流程只允许局部步骤变化。我之前上线的钉钉OA工单审批就是这么个场景。核心操作就三个创建OA工单、取消OA工单、钉钉回调处理。三个操作的流程是固定的。公司OA工单类型很多补货订货审批、门店营业时间变更审批、商品售罄审批等一共6种以后还会更多。流程固定每种工单只有表单内容和审批后的处理不同类型还很多用继承是自然选择。有人可能会说为什么不用组合把对接OA工单的逻辑拆成校验器、表单组装器、创建器等几个独立组件每种工单类型组合这些组件再自己组织调用顺序不也能实现吗。其实我一开始也这么想过。能实现。但流程对不对更多得靠每个实现类自己盯着。每个实现类仍然拥有重新排列步骤的自由有人可能忘了校验有人可能先组装表单再组装基本信息。这些错误在代码审查时不一定能发现上了生产就是事故。我的做法是直接在父类把流程锁死。看一下AbstractDingTalkWorkFlowStrategy里createWorkFlow方法的核心流程// 校验 → 组装基本信息 → 组装表单 → 创建this.checkCanCreateWorkFlow(createWorkFlowCommand.getBizId());CreateWorkFlowDTOdtothis.assembledBaseInfo(createWorkFlowCommand);this.assembledFormComponents(dto);this.doCreateWorkFlow(dto);校验、组装基本信息、组装表单、创建四步顺序写死在父类里。子类碰不到流程只能实现变化点。子类这边就简单了只剩两件事。父类声明了两个抽象方法// 子类必须实现的两个变化点protectedabstractvoidassembledFormComponents(CreateWorkFlowDTOcreateWorkFlowDTO);protectedabstractvoidapprovalCompleted(DingTalkWorkFlowRecordEntityentity);子类必须实现这两个方法也只能实现这两个方法。补货订货的子类ReplenishmentOrderWorkFlowStrategy在assembledFormComponents里组装补货订货的表单数据在approvalCompleted里审批通过后发钉钉消息通知相关人员。门店营业时间变更的子类ShopTimeChangeApplyWorkFlowStrategy组装的是门店时间变更的表单审批通过后回调门店系统更新状态。每个子类只关心自己那部分差异cancel、handleCallBack这些固定逻辑在父类写一次6个子类共享不用复制。还有一个细节。checkCanCreateWorkFlow这个方法在父类里是空的补货订货的子类重写了它检查是否重复申请、是否超过次数限制。不需要校验的子类什么都不用做。需要的人重写不需要的人不管。扩展点固定在流程里不会因为忘记调用而出问题。至于组合。你要组织的是一组独立能力各管各的组合更合适。支付渠道支付宝、微信、银联每个都是独立能力可以组合进任何业务。消息通知钉钉、短信、邮件也是独立能力。组合让每个能力独立变化、自由拼装不需要绑死在一条流程里。不过继承让我坚持这么用的还不是这些。流程一旦变化所有子类应该一起变化。比如审批流程以后统一加一步权限校验在父类的createWorkFlow里加一行checkPermission()6个审批类型自动生效不用逐个改。你想想换成组合的话每个实现类都得自己加这一步漏掉一个就是线上漏洞。继承共享的不只是今天的代码更是未来的修改。值得继承的是未来应该一起演进的流程。组合优先于继承没有错。它批评的是把继承当作默认复用机制为了少写几行代码就建继承关系。问题从来不在继承在于你为什么建立这层继承。判断什么时候用组合、什么时候用继承我一般就看两件事你面对的是几个独立能力还是一条稳定流程里的几个变化点这段逻辑未来是各自独立变化还是应该随着所有实现一起演进组合负责组织彼此独立的能力继承负责约束需要一起演进的流程。如果一段逻辑未来应该随着所有实现一起变化继承往往比组合更自然。面试遇到这个问题能从业务场景和未来演进的角度给判断.学过设计模式的话应该看出来了上面的写法就是模板方法模式。设计模式只是后来的人给这种思想起了名字。业务怎么变代码就该怎么设计。