模板方法用组合还是继承多平台电子面单的抉择摘要模板方法模式通常用抽象类定义算法骨架但在多平台电子面单架构中我们却选择了“组合”方式——WaybillFetchTemplate通过注入策略对象来固定流程而非让子类继承。本文从真实代码出发对比两种实现方式拆解为什么在这个场景下“组合优于继承”并总结出模板方法选型的决策框架。系列导航系列开篇从“能跑就行”到“整洁架构”上一篇解析器职责分离改造本文模板方法的组合与继承抉择后续京东、拼多多等平台专项篇文章目录模板方法用组合还是继承多平台电子面单的抉择一、一个经典问题模板方法怎么写二、我们的选择组合式模板三、为什么不用继承四个核心理由理由一策略已经用接口独立了继承会造成冗余理由二避免类爆炸理由三运行时灵活性理由四更好的可测试性四、对比表格继承 vs 组合实现模板方法五、什么时候该用继承六、延伸顺丰子母件——模板层分支的威力七、总结模板方法选型决策框架八、系列导航与参考延伸阅读Java 23种设计模式实战系列九、一起交流共同进步一、一个经典问题模板方法怎么写模板方法模式Template Method Pattern是 GoF 23 种设计模式中最简单也最常用的行为型模式之一。它的核心思想是在父类中定义算法骨架将某些步骤延迟到子类实现。教科书上的标准实现通常是这样的// 抽象父类定义算法骨架publicabstractclassAbstractWaybillFetcher{// 模板方法固定流程publicfinalvoidfetch(){validate();// 1. 校验ObjectrequestbuildRequest();// 2. 构建请求StringresponsecallApi(request);// 3. 调用APIparseResponse(response);// 4. 解析响应save();// 5. 持久化}privatevoidvalidate(){/* 通用校验 */}protectedabstractObjectbuildRequest();// 子类实现protectedabstractvoidparseResponse(Stringresponse);protectedabstractvoidsave();protectedStringcallApi(Objectrequest){/* 通用调用 */return;}}// 子类奇门实现publicclassQiMenWaybillFetcherextendsAbstractWaybillFetcher{OverrideprotectedObjectbuildRequest(){/* 构建奇门SDK请求 */}OverrideprotectedvoidparseResponse(Stringresponse){/* 解析奇门响应 */}Overrideprotectedvoidsave(){/* 保存运单 */}}这种写法用了二十多年看起来毫无问题。但在我们的多平台电子面单架构中却主动放弃了这个“标准答案”选择了一个看似“离经叛道”的做法用组合代替继承。设计模式视角模板方法模式是 GoF 23 种行为型模式之一理解它的两种实现方式继承 vs 组合对于判断“何时用继承、何时用组合”至关重要。在《Java 23种设计模式从踩坑到精通》系列的第22篇策略模式和第23篇模板方法模式中我深度对比了两种模式的适用场景与选型边界欢迎延伸阅读。二、我们的选择组合式模板在我们的架构中模板类WaybillFetchTemplate是一个普通的、非抽象的类它不要求任何子类继承它publicclassWaybillFetchTemplate{privatefinalRequestStrategyrequestStrategy;privatefinalParseStrategyparseStrategy;privatefinalExceptionStrategyexceptionStrategy;privatefinalApiInvokerapiInvoker;privatefinalWaybillPersistencepersistence;// 通过构造函数注入三个策略对象publicWaybillFetchTemplate(RequestStrategyreq,ParseStrategyparse,ExceptionStrategyex,ApiInvokerinvoker,WaybillPersistencepersist){this.requestStrategyreq;this.parseStrategyparse;this.exceptionStrategyex;this.apiInvokerinvoker;this.persistencepersist;}publicbooleanexecute(WaybillContextctx){// 1. 构建请求策略ObjectrequestrequestStrategy.buildRequest(ctx);// 2. 调用APIApiInvokerStringresponseapiInvoker.invoke(ctx,request,traceId);// 3. 业务异常判断策略if(!exceptionStrategy.isBusinessSuccess(response)){markException(ctx.getTicket(),exceptionStrategy.extractErrorMsg(response));returnfalse;}// 4. 解析响应策略ListDetaildetailsparseStrategy.parseResponse(ctx,response);if(details.isEmpty()){markException(ctx.getTicket(),未获取到运单号);returnfalse;}// 5. 持久化persistence.saveAndBind(ctx.getTicket(),details,ctx.isFirst());returntrue;}}使用时通过工厂注入不同平台的策略实例// 奇门RequestStrategyreqnewQiMenRequestStrategy();ParseStrategyparsenewQiMenParseStrategy();ExceptionStrategyexnewQiMenExceptionStrategy();WaybillFetchTemplatetemplatenewWaybillFetchTemplate(req,parse,ex,invoker,persistence);template.execute(ctx);// 抖音reqnewDouYinRequestStrategy();parsenewDouYinParseStrategy();exnewDouYinExceptionStrategy();templatenewWaybillFetchTemplate(req,parse,ex,invoker,persistence);template.execute(ctx);可以看到WaybillFetchTemplate没有任何abstract方法也没有任何子类。它的“可变部分”全部通过构造函数注入的策略对象来实现。三、为什么不用继承四个核心理由理由一策略已经用接口独立了继承会造成冗余在我们的架构中平台差异已经被三个策略接口完美隔离publicinterfaceRequestStrategy{ObjectbuildRequest(WaybillContextctx);}publicinterfaceParseStrategy{ListDetailparseResponse(WaybillContextctx,Stringresponse);}publicinterfaceExceptionStrategy{booleanisBusinessSuccess(Stringresponse);}如果再用一个抽象的AbstractWaybillFetchTemplate让各平台子类去实现等于把同样的差异逻辑在两个地方重复定义。子类既要实现策略接口又要覆写模板的抽象方法——这是典型的“过度抽象”。理由二避免类爆炸假设我们用继承方式实现每接入一个新平台就要写一个子类AbstractWaybillFetchTemplate ├── QiMenWaybillFetchTemplate ├── DouYinWaybillFetchTemplate ├── DouYinDaiFaWaybillFetchTemplate ├── JDWaybillFetchTemplate └── PDDWaybillFetchTemplate十几个平台就是十几个子类而它们之间的唯一区别只是策略不同。用组合只需一个WaybillFetchTemplate通过注入不同策略实例即可覆盖所有平台。理由三运行时灵活性组合允许在运行时动态替换策略。比如通过工厂根据平台编码返回不同的策略组合RequestStrategyreqstrategyFactory.getRequestStrategy(platformCode,original);ParseStrategyparsestrategyFactory.getParseStrategy(platformCode,original);ExceptionStrategyexstrategyFactory.getExceptionStrategy(platformCode,original);WaybillFetchTemplatetemplatenewWaybillFetchTemplate(req,parse,ex,...);继承的类关系在编译期就固定了无法做到这种运行时动态组装。理由四更好的可测试性组合式模板可以单独测试注入 Mock 的策略对象即可无需启动 Spring 容器或继承任何基类TestpublicvoidtestTemplate(){RequestStrategymockReqctx-mock request;ParseStrategymockParse(ctx,resp)-Collections.singletonList(mockDetail);ExceptionStrategymockExresp-true;WaybillFetchTemplatetemplatenewWaybillFetchTemplate(mockReq,mockParse,mockEx,mockInvoker,mockPersistence);booleanresulttemplate.execute(ctx);assertTrue(result);}如果用继承测试时需要创建匿名子类或启动整个依赖链成本高得多。四、对比表格继承 vs 组合实现模板方法维度继承方式组合方式代码量每个平台一个子类一个模板类 策略接口扩展性新增平台需新增子类新增平台只需新增策略实现灵活性编译期绑定运行时动态替换测试性需创建子类实例直接 Mock 策略注入类数量N 个平台 → N 个子类1 个模板类复用性子类间无法复用策略策略可跨平台组合适合场景步骤固定、差异集中、子类数量少步骤固定、差异分散、实现类数量多五、什么时候该用继承组合不是银弹。在以下场景中继承方式仍然更合适需要共享实例状态比如父类持有commonDao、logger等字段子类直接使用。这正是我们架构中DefaultBaseManager用抽象类的原因。子类数量极少只有两三个子类时继承的简洁性优于组合的灵活性。步骤之间有强关联模板的某些步骤需要访问父类的内部状态不适合拆分成独立接口。在我们的电子面单架构中策略已经通过接口独立且平台数量可能增长到十几个组合的优势远大于继承。六、延伸顺丰子母件——模板层分支的威力组合式模板还有一个额外优势在模板层增加流程分支非常自然。顺丰超过 10 件需要走子母件模式分批调用 API我们的处理方式是直接在WaybillFetchTemplate中增加一个executeSFMoreTen方法publicbooleanexecute(WaybillContextctx){if(isSFMoreTen(ctx)){returnexecuteSFMoreTen(ctx,traceId);// 子母件分支}returnexecuteNormal(ctx,traceId);// 普通分支}如果用继承子母件逻辑要么重复在每个子类中实现要么在父类中增加复杂的分支判断很容易失控。组合方式让模板层成为唯一的流程控制点分支逻辑清晰可维护。七、总结模板方法选型决策框架结合我们的实战经验可以提炼出以下决策框架先看差异是否已经抽象成接口如果平台差异已经通过策略接口独立优先用组合。看平台数量超过 5 个平台时组合避免类爆炸的优势明显。看是否需要运行时灵活替换需要则用组合不需要则继承也可。看是否需要共享实例状态如果需要且差异未接口化继承更合适。面试绝杀一句话模板方法模式的核心是“固定流程骨架差异化具体步骤”。这个骨架既可以用继承实现也可以用组合实现。在我们的多平台电子面单架构中因为平台差异已经通过三层策略接口独立组合方式更灵活、更简洁。八、系列导航与参考本篇文章是「电商多平台电子面单对接实战」的第八篇设计模式篇聚焦模板方法模式在真实项目中的选型实践。系列文章目录开篇从“能跑就行”到“整洁架构”第一篇奇门对接顺丰电子面单第二篇抖音代发电子面单对接第三篇抖音普通订单电子面单对接第四篇多平台统一架构设计第五篇策略工厂复合Key路由改造第六篇快递公司前置校验改造第七篇解析器职责分离改造第八篇模板方法的组合与继承抉择本文后续京东、拼多多等平台专项篇延伸阅读Java 23种设计模式实战系列本文中模板方法选型的核心——组合优于继承以及背后涉及的策略模式、模板方法模式等设计理念在《Java 23种设计模式从踩坑到精通》系列中有更体系化的讲解。如果你对以下问题感兴趣推荐延伸阅读策略模式 vs 模板方法模式两者如何选择各适用于什么场景组合优于继承六大设计原则中的这一条在真实项目中如何落地工厂模式 策略模式如何配合实现复合Key路由《Java 23 种设计模式从踩坑到精通》系列开篇从踩坑到精通 —— 总览与导航策略模式 —— 算法族的封装与切换模板方法模式 —— 定义算法骨架交给子类填充细节学习建议电子面单系列侧重业务落地设计模式系列侧重理论体系。两者搭配阅读既能应对面试又能反哺项目形成“理论→实战”的闭环。九、一起交流共同进步技术之路一个人走得快一群人走得远。如果您在项目中也有过“继承还是组合”的纠结希望本文的实战经验能给您带来启发。关注我点击上方“关注”第一时间获取系列更新推送。留言讨论您在实际项目中选择过组合式模板方法吗遇到过什么坑欢迎在评论区分享。分享转发如果本文对您有帮助请点赞、收藏、分享让更多同行看到。标签#Java#设计模式#模板方法模式#组合优于继承#电子面单#架构设计#重构