恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

模板方法模式深度解析:从原理到实战,告别重复代码

  • 首页
  • 资讯中心
  • /
  • 模板方法模式深度解析:从原理到实战,告别重复代码

相关资讯

Clawdbot:大模型驱动机械臂的具身智能参考实现解析 2026/9/8 19:57:29
Storybook 全局源码变换:用 parameters.docs.source.transform 让文档中的代码片段统一走 Prettier 2026/9/8 19:57:29
避雷❗2026论文双审别乱套AI!OKBIYE才是真合规选手 2026/9/8 19:57:29

最新资讯

中文文本分类的分层建模实战:CNN+RNN+GCN+BERT协同设计
5分钟用amis搭起后台管理系统:JSON配置生成页面
在 Visual Studio 里接入 Ace Data Cloud:几分钟配置 AI 编程助手
工业机器视觉闭环系统:预处理、瞄准与通信的工程实践
amis低代码从入门到实战:JSON配置页面搭建后台管理系统的完整流程
速腾聚创RS-Lidar-16拆解:16线机械激光雷达技术方案解析

今日推荐

Redis缓存与离线预计算在大数据处理中的实战应用
Android 12热启动闪屏排查:从冷热启动差异到官方SplashScreen避坑指南
加密资产价值投资:原理、方法与实战策略

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

模板方法模式深度解析:从原理到实战,告别重复代码

发布时间:2026/9/8 19:57:29
模板方法模式深度解析:从原理到实战,告别重复代码 1. 模板方法模式套路中的套路先问个问题你写代码的时候有没有遇到过这种情况——两个方法长得几乎一模一样只有中间几步不一样剩下的都是同样的顺序、同样的判断、同样的资源释放。大部分人第一反应是“复制一份改改”结果后续改需求的时候同一个逻辑要改两遍三遍改漏一处就出bug。模板方法模式就是专门治这个毛病的。它属于行为型设计模式核心思想特别朴素把一套固定流程的骨架定义在父类里把其中某些步骤的具体实现延迟到子类中完成。说白了就是“流程我定细节你填”。写这篇文章的动机是因为不管在技术社区还是实际面试里模板方法模式总是被一句话带过“就是父类定义方法子类实现呗”。但真到了用的时候很多人要么不知道该在哪用要么把模板方法和策略模式、工厂方法搞混要么写出了一堆毫无扩展性的“伪模板”。这篇文章我会把这个模式从原理、实战到坑点完整盘一遍适合刚学完Java基础、准备系统学设计模式的人也适合在项目里被重复代码折磨到想重构的开发者。看完你至少能回答一个问题什么场合该用模板方法什么场合用了反而是在给自己挖坑。2. 模板方法模式的设计思路拆解2.1 从一个日常场景说起想象一下你开一家奶茶店。不管是做珍珠奶茶、柠檬绿茶还是芝士奶盖流程都是固定的烧水、放茶叶/原料、加奶/加糖、封口、打包。但如果每个店员都把“烧水”这个动作在每款饮品里重复写一遍那不叫灵活那叫灾难。正确的做法是店长把整个流程定成标准作业程序SOP员工只需在“放什么原料”“加什么辅料”这两步上按各门店的需求具体操作。模板方法模式就是这个SOP在代码里的映射。固定不变的步骤烧水、封口、打包放在父类里统一定义可以变化的步骤放什么茶底、加什么小料交给具体的子类去实现。2.2 模式的核心角色和结构这个模式涉及的角色不多但每个角色都有明确的分工抽象类AbstractClass定义了模板方法template method也就是那个不允许子类修改的整体算法骨架。同时声明了若干抽象方法或钩子方法hook这些是需要子类去填充的“可变化部分”。具体子类ConcreteClass实现父类声明的抽象步骤完成算法中与特定子类相关的行为。它可以重写钩子方法来干预流程的某个支点但不能重写模板方法本身的执行顺序。用Java表达出来骨架大概是这样的public abstract class AbstractOrderProcessor { // 模板方法定义整个处理流程一般用final修饰防止子类篡改顺序 public final void processOrder() { validateOrder(); deductStock(); doPayment(); if (customerVip()) { // 钩子方法控制流程的可选分支 giveExtraGift(); } sendNotification(); } // 以下是固定逻辑可直接实现 private void validateOrder() { System.out.println(校验订单基本信息...); } private void sendNotification() { System.out.println(发送通知给用户...); } // 以下是可变逻辑必须由子类实现 protected abstract void deductStock(); protected abstract void doPayment(); // 钩子方法默认不额外赠送子类按需覆写 protected boolean customerVip() { return false; } protected void giveExtraGift() { System.out.println(赠送额外小礼品); } }这里有几个关键细节值得注意平台的一个支点钩子方法都可以被抽象。钩子方法本身有个特点——它在父类里通常有默认实现返回一个默认值或空实现子类可以根据业务需要决定要不要覆盖它。在订单处理例子里customerVip()就是一个钩子普通订单不触发赠送逻辑VIP订单的子类重写它返回true流程中就多了一个赠送步骤。这种机制让父类在不修改模板方法的前提下拥有了对流程分支的控制能力。2.3 为什么这种设计能站住脚很多人会问我把所有逻辑都写进一个类里不也能解决同样的问题吗没必要非用模式吧。确实如果一个业务只在一个地方出现而且永远不会变那直接写死当然没问题。但现实是业务流程的骨架往往是稳定的而细节整天在变。拿订单处理举例今天要接入支付宝明天要接入微信支付后天可能还要支持礼品卡。如果每次对接一个新支付渠道都要把“校验订单、扣库存、发通知”整个流程重写一遍那代码量的膨胀速度和维护成本会非常可观。模板方法模式真正的价值在于它做到了两件事第一把不变和变化分离。稳定流程沉淀在高层抽象中变化的部分以抽象方法或钩子形式向下开放。这样一来新增一个业务变体不需要动老代码只要加一个新的子类完全符合开闭原则。第二控制反转的一种温和实现。传统写法是“上层调用下层”而模板方法模式是“父类在固定的时机调用子类的方法”。子类开发者不需要关心什么时机被调只需要专注“把这一步做对”。这种思想在很多框架里都有体现比如Spring的JdbcTemplate、Servlet中的doGet/doPost本质都是模板方法的应用。第三消灭重复但保留差异。如果用组合或回调函数硬写虽然也能实现但每个子类都要重复编写流程顺序代码。模板方法把顺序固定住了子类永远写不出“先支付后校验”这种错乱逻辑。这就是为什么要用final修饰模板方法——它是在刻意地限制子类的自由以保证整体流程的一致性。2.4 模板方法模式与策略模式的边界差异这是最容易搞混的一组对比。模板方法模式和策略模式看起来都像“把不同实现抽出去”但维度完全不同。策略模式的核心是算法可以整体替换强调的是“把一个算法的不同实现封装成策略客户端在运行时选择”。比如排序算法你可以切换到快排、归并或者冒泡整个算法都换了选择权在使用者手里。模板方法模式的核心是算法骨架固定只有局部步骤可变强调的是“流程不能改但流程中的某些环节可以定制”。选择权在父类设计者手里子类只是填空。举一个更直观的例子做菜。策略模式是你决定“今天吃火锅还是烧烤”——整个烹饪方式都变了由你来选。模板方法模式是“今天定了吃炒菜”——炒菜的流程都一样热油、下锅、调味、出锅但具体放什么菜、什么调料由执行的人决定。这两种模式并不互斥甚至经常混用一个模板方法内部某一步骤可能就是一个策略接口的调用点。3. 核心细节解析把每个“为什么”都讲透3.1 抽象方法、具体方法、钩子方法的定位区别很多刚接触模板方法的人会困惑“父类里到底哪些方法应该用abstract哪些应该用具体实现哪些要用钩子”我的经验是从变化频率来反推所有业务场景都一模一样未来也不会改变的逻辑直接写成private或具体方法子类完全不需要感知。这些是“骨”。每个子类几乎必然不同不实现就没办法跑通业务流程的逻辑写成protected abstract。这些是“必需的变化点”。大部分场景有默认行为但某些特殊场景需要干预流程或增加分支的逻辑写成protected的具体方法并给出默认实现也就是“钩子”。这些是“可选的变化点”。举一个更实际的场景假设你在做一套报表导出功能流程是准备数据 → 格式化表头 → 填充数据行 → 输出文件。不同的报表类型数据准备和数据格式化肯定不一样所以这两个应该是抽象方法。输出文件的方式PDF和Excel不一样但对于某些报表可能两者都支持那就可以做成钩子默认输出Excel子类重写后就输出PDF。表头格式可能大部分报表都用“默认样式”但个别报表需要自定义这也是钩子。把三条规则记住设计的时候就不用在“这个该不该抽象”上纠结太久。3.2 为什么模板方法要用final或非虚方法修饰在Java里默认情况下所有非static方法都可以被重写所以如果你不显式地做限制子类完全可以把模板方法本身覆盖掉那整个“骨架稳定”的设想就崩塌了。所以实战里模板方法通常要用final修饰。Java里这样写public final void processOrder() { ... }C里则是在模板方法上不加virtual。如果父类模板方法被子类重写很多人会误以为这是“扩展”实际上这是“破坏流程”——子类可以随意改变步骤顺序甚至漏掉某一步后续排查问题的难度会指数级上升。有人会反驳“那如果我就是想让子类在某个步骤前后插入一些操作呢”答案是不要通过允许重写模板方法来实现而应该在模板方法内部预留钩子。比如在验证订单之前加一个beforeValidate()的钩子在发送通知之后加一个afterNotify()的钩子。这样既保留了扩展点又守住了流程骨架。3.3 钩子方法的正确使用姿势钩子方法是模板方法模式中最容易用歪的工具。用得好它是灵活的扩展点用不好它就是一堆形同虚设的空方法。一个常见的问题是钩子方法太多。有经验的Java开发者应该都写过类似这样的类public abstract class AbstractSomething { public final void run() { beforeStart(); doStart(); afterStart(); beforeProcess(); doProcess(); afterProcess(); beforeEnd(); doEnd(); afterEnd(); } protected void beforeStart() {} protected void afterStart() {} // 省略N个空实现... }这种代码表面上“预留了所有扩展点”实际上子类根本不知道哪些方法该重写代码的可读性和可维护性骤降。钩子方法应该控制在“业务上确实存在可选分支”的位置不是为了“万一以后要用”而滥用。我之前接手过一个老项目一个模板类里钩子方法多达12个每个都是空实现导致子类里前五个方法全是空的根本不知道设计者想让人扩展什么。另一个常见的误用是在钩子方法里强行做流程控制。钩子方法的定位是“让子类影响流程的分支选择”而不是“让子类修改流程的执行逻辑”。如果你发现子类在钩子里做了大量复杂操作那可能是抽象粒度设计得不对应该把那段操作提升为抽象方法而不是塞进钩子里。3.4 抽象类 vs 接口这个模式怎么选说到父类的载体通常有两种选择抽象类或接口。模板方法模式要求父类持有完整的、可复用的算法骨架里面既要有具体方法固定流程又要有抽象方法可变步骤还要有默认实现的方法钩子。这个需求用抽象类来实现是最自然的——Java 8之后的接口虽然支持default方法但把业务骨架塞进接口会导致接口职责过重语义上也不清晰。C的纯虚类配合非虚的成员函数也能实现同样的效果但C里模板方法实现时要特别注意如果模板方法在基类中调用虚函数要在非虚成员函数里调用虚函数这样才能保证“基类定义框架、派生类实现细节”的预期。有些C新手会把模板方法本身写成虚函数然后基类的析构函数里调用它结果直接踩了“构造函数/析构函数里不要调用虚函数”的坑。4. 用代码实战一个“数据导出器”的完整实现4.1 场景设定与整体设计光讲理论没有画面感我完整地走一个小项目做一个多格式数据导出器。需求是系统里有订单数据、用户数据、商品数据需要支持导出成CSV和JSON两种格式。每种数据的导出逻辑不一样但流程大体一致查询数据 → 加工数据 → 生成表头/元信息 → 格式化输出 → 写出文件 → 记录日志。如果不用模板方法最简单的写法是写六个类OrderCsvExporter、OrderJsonExporter、UserCsvExporter... 每类里复制粘贴一套查询、格式化、输出代码。如果有人导出了第三种格式那代码量直接翻倍。用模板方法的话整个结构会清晰很多public abstract class AbstractDataExporterT { // 模板方法定义导出的固定流程 public final File export(String fileName) throws IOException { ListT data loadData(); // 1. 加载数据子类实现 ListString[] rows formatData(data); // 2. 按格式加工子类实现 String header buildHeader(); // 3. 生成表头子类实现 String content serialize(header, rows); // 4. 序列化父类固定 File file writeToFile(fileName, content); // 5. 写文件父类固定 writeLog(file.getName(), data.size()); // 6. 日志记录父类固定 return file; } // 可变步骤由具体导出器实现 protected abstract ListT loadData(); protected abstract ListString[] formatData(ListT data); protected abstract String buildHeader(); // 固定步骤所有导出器都一样的逻辑 protected String serialize(String header, ListString[] rows) { StringBuilder sb new StringBuilder(header); for (String[] row : rows) { sb.append(\n).append(String.join(,, row)); } return sb.toString(); } protected File writeToFile(String fileName, String content) throws IOException { File file new File(fileName); try (FileWriter writer new FileWriter(file)) { writer.write(content); } return file; } protected void writeLog(String name, int count) { System.out.println(导出文件: name , 数据量: count); } // 钩子方法默认导出不带BOMUTF-8 with BOM可以让Excel正确识别中文CSV protected boolean needBom() { return false; } }这个设计里serialize和writeToFile对所有导出器都是一样的所以放在父类里直接实现。loadData、formatData、buildHeader是每个业务类型各不相同的所以定义成抽象方法。4.2 具体子类的实现与细节处理接下来写两个具体的导出器。订单导出为CSVpublic class OrderCsvExporter extends AbstractDataExporterOrder { Override protected ListOrder loadData() { // 实际项目中这里是查数据库或调用远程服务 return Arrays.asList( new Order(1001, 张三, 299.0), new Order(1002, 李四, 159.0) ); } Override protected ListString[] formatData(ListOrder data) { ListString[] rows new ArrayList(); for (Order order : data) { rows.add(new String[]{ String.valueOf(order.getId()), order.getCustomerName(), String.valueOf(order.getAmount()) }); } return rows; } Override protected String buildHeader() { return 订单ID,客户姓名,订单金额; } Override protected boolean needBom() { return true; // Excel打开中文CSV不乱码 } }用户导出为JSONpublic class UserJsonExporter extends AbstractDataExporterUser { Override protected ListUser loadData() { return Arrays.asList( new User(u01, 小张, vip), new User(u02, 小王, normal) ); } Override protected ListString[] formatData(ListUser data) { ListString[] rows new ArrayList(); for (User user : data) { rows.add(new String[]{user.getId(), user.getName(), user.getLevel()}); } return rows; } Override protected String buildHeader() { return 用户ID,昵称,会员等级; } Override protected String serialize(String header, ListString[] rows) { // 这里偷个懒JSON序列化直接调Jackson会更规范为了聚焦模式本身这里手动拼一个简单的JSON StringBuilder sb new StringBuilder([); for (int i 0; i rows.size(); i) { String[] row rows.get(i); if (i 0) sb.append(,); sb.append({\id\:\).append(row[0]) .append(\,\name\:\).append(row[1]) .append(\,\level\:\).append(row[2]) .append(\}); } return sb.append(]).toString(); } }注意一个有意思的地方UserJsonExporter重写了serialize方法因为它输出的不是CSV逗号分隔格式而是JSON数组结构。这意味着父类的模板方法流程对其他子类仍然成立但某个步骤的具体行为被子类替换了。这正是模板方法模式的精髓骨架稳定部分步骤可以通过继承实现多态。不过也要提醒一下UserJsonExporter重写serialize的行为容易被人滥用。如果每个子类都重写父类的固定步骤那“固定步骤”就形同虚设了。所以在设计阶段就要想清楚哪些步骤是“真的固定”哪些是“当前看起来固定但以后可能变”。前者放父类写死后者考虑从模板方法中拆出去或者定义为钩子。4.3 使用模板方法模式时的访问控制推荐一个比较典型的实战细节是父类的模板方法建议用public或protected final固定步骤用private可变步骤用protected abstract钩子用protected。为什么这么分final是为了不让子类篡改流程顺序。private是为了告诉子类“这些细节你不用管你只管填空”。protected是为了允许子类在实现抽象方法时共享一些父类的内部工具方法。绝对不要用public暴露所有抽象步骤否则外部代码可以直接绕过模板方法单独调用loadData()或serialize()流程的完整性就会被破坏。4.4 结合“钩子方法”的场景扩展导出后压缩现在假设新的需求来了有些导出场景要求把生成的文件打包成zip再发送给用户。老流程是“写出文件 → 记录日志”新流程是“写出文件 → 打包压缩 → 记录日志”。在不改变模板方法骨架的前提下怎么加这个分支答案是加一个钩子方法public final File export(String fileName) throws IOException { ListT data loadData(); ListString[] rows formatData(data); String header buildHeader(); String content serialize(header, rows); File file writeToFile(fileName, content); if (needZip()) { // 新增钩子 file zipFile(file); // 压缩处理 } writeLog(file.getName(), data.size()); return file; } protected boolean needZip() { return false; } protected File zipFile(File file) throws IOException { // 使用ZipOutputStream压缩的逻辑 return new File(file.getAbsolutePath() .zip); }需要压缩的子类只需要重写needZip()返回true同时配合重写zipFile()选择压缩算法。不需要压缩的子类完全不受影响。这就是钩子方法的典型用法——给流程增加一个可选分支而不是让子类去判断“要不要压缩”这种业务逻辑。5. 模板方法模式在主流框架源码中的身影5.1 Java AbstractList集合框架里的模板方法几乎所有学过Java集合的人都用过AbstractList但很少人注意到它就是模板方法模式的经典范例。AbstractList里实现了add()、get()、remove()等一系列方法其中最核心的是iterator()方法它定义了迭代器的整体逻辑但实际按索引取元素的操作get(int index)被定义成抽象方法由ArrayList、LinkedList等子类各自实现。这就是一个教科书级别的模板方法应用固定了“迭代器怎么遍历”的骨架具体“容器怎么存数据”由子类负责。正因为有这层抽象你才可以用统一的Iterator接口遍历所有List的实现类。5.2 Android中的AsyncTask和BaseActivity在Android开发里模板方法模式几乎是无处不在。经典的AsyncTask定义了onPreExecute→doInBackground→onProgressUpdate→onPostExecute的完整执行流程子类只需要实现doInBackground和onPostExecute就行。还有一个常见的实践是BaseActivity把onCreate中的setContentView、initView、initData抽成方法每个页面按顺序重写这也是模板方法的一种变体。很多人一开始写Android代码时没意识到Activity本身就给了你一大堆“钩子方法”——onResume()、onPause()、onDestroy()——这些就是系统在特定生命周期阶段回调你的模板方法。你不需要关心系统什么时候调、以什么顺序调你只需要实现自己关心的部分。5.3 Spring框架中的JdbcTemplate和RestTemplateJdbcTemplate是模板方法思路在数据访问层的标准化样本。它把JDBC的固定流程获取连接、创建Statement、处理ResultSet、关闭连接封装在内部把变化的部分——SQL语句、参数绑定、结果映射——留给你通过回调接口去实现。虽然JdbcTemplate在具体实现上用了回调而不是继承的方式但设计思想上和模板方法模式一脉相承。RestTemplate用来发送HTTP请求时它把“构建请求URL”、“设置header”、“处理响应状态码”这些步骤做了流程化封装你这边的职责仅限于指定URL和反序列化类型。可以说Spring的模板类本质上就是把模板方法模式从“继承范式”演化成了“组合范式”。5.4 工作流和中间件里的模板方法思维如果你写过工作流引擎比如Flowable或Activiti的流程定义你会发现整个流程引擎的设计也有很强的模板方法印记启动流程、执行节点、判断网关、结束流程这些步骤被流程引擎固定住了而你只需要通过配置或监听器去“填空”。包括微服务里的统一API网关请求进来后的鉴权、限流、路由、转发这个顺序也是一条固定流水线每个环节都留了扩展点。从这些源码实例能得出一个结论模板方法模式的真正威力不在于某一个类写得有多漂亮而在于它为团队定下了“流程约定”。新人接手的成本低出错概率小因为大多数人不需要修改骨架本身。6. 常见问题与排查技巧实录6.1 子类重写了模板方法流程被破坏了怎么办这是我在Code Review里最常遇到的问题。有些开发者看到父类里的final修饰符第一反应是“怎么还带final的”顺手就删了然后按自己的理解重写了一遍模板方法。结果就是同一个流程出现了多份实现后续维护时在A子类里改了顺序在B子类里忘了改Bug就开始连锁出现。排查这个问题没什么高深技巧Code Review阶段就要立好规矩模板方法一律不允许子类重写父类中用final声明。如果业务上真的需要对整个流程做大范围调整说明这个业务和原来的流程根本不是同一种流程应该另立一个抽象父类而不是在子类里搞特殊。6.2 钩子方法没生效子类重写了但流程没变化钩子失效的原因一般有三个第一调用的位置不对。检查钩子方法是不是真的在模板方法中被调用了。有时候你定义了protected boolean needZip()但模板方法里忘了加if (needZip())这段逻辑那子类重写得再努力也白搭。第二方法签名不匹配。子类重写时把钩子方法的返回类型或参数列表改错了导致实际上没有触发重写而编译器也没报错因为你可能把Override注解漏了。所以钩子方法务必加Override注解让编译器帮你把关。第三模板方法被重写了。如果某个子类重写了整个模板方法那原本父类里的钩子调用肯定也就不存在了。这又回到了第一条final写在前面。6.3 继承层级过深模板方法变成了“俄罗斯套娃”有些项目里抽象父类下面还有抽象子类抽象子类下面又有抽象子类每一层都加几个抽象方法或钩子等到具体业务类的时候你根本分不清哪些是必须实现的哪些是可选的哪些其实已经有人实现过了。遇到这种情况我一般会建议如果继承了五层以上还没到具体类先停下来重构。常见的重构手段是把“可变的步骤”从继承中解放出来用策略接口或函数式接口替代部分抽象方法。模板方法模式擅长处理“流程骨架不变”的场景但复杂的业务流程不适合用一棵极度深层的继承树来表达。组合优于继承在这里同样适用。6.4 钩子方法过多导致子类实现被“架空”前面我提过钩子方法不要滥用。一旦钩子方法超过三五个子类实现时的认知负担就会明显变大。新来的同事要在十几个空方法里到处找“到底哪个是需要我重写的”效率很低。我在实际项目中用过的一个判断标准是如果某个钩子方法在所有的子类中从未被重写过那它就是多余的。如果十个子类里有八个都不重写某个钩子那这个钩子的使用价值也需要重新审视——有可能是这个变化点不适合放在继承体系里而应该放在配置文件中。6.5 模板方法模式配合设计模式的组合套路最后分享一个实战中的应用技巧模板方法模式很少单独出场通常和工厂方法、策略模式组合使用。比如上面的导出器例子客户端在运行时需要根据导出类型创建对应的导出器这里就可以加一个工厂类public class ExporterFactory { public static AbstractDataExporter? create(String type) { if (order_csv.equals(type)) { return new OrderCsvExporter(); } else if (user_json.equals(type)) { return new UserJsonExporter(); } throw new IllegalArgumentException(不支持的导出类型: type); } }这样客户端代码就和具体导出器解耦了新增导出格式只需要新增一个子类改一行工厂逻辑。配合策略模式你还可以把不同导出器的“格式选择”动态化让用户在页面上选择不同导出格式时不需要重启服务就能生效。7. 期末复习与面试模板方法模式的高频考点看到热搜里有“设计模式期末”和“java设计模式”这些关键词我猜不少读者是为了应付考试或者面试来搜这篇文章的。这部分就给备考的同学划几个重点。7.1 期末考试常考的三个方向概念题给一个场景描述要求说出适合用什么设计模式。比如“某个业务流程步骤固定但部分步骤有多种实现方式不同实现方式之间不影响整个流程” —— 这就是模板方法模式的典型描述。注意和策略模式的区分策略模式强调“整组算法可选”“客户端可切换”模板方法强调“骨架固定”“子类只能填空”。画图题要求画出模板方法模式的UML类图。核心要素是抽象类、具体子类、模板方法、抽象操作注意箭头方向的依赖关系要标对。代码填空题给一个半成品抽象类要求补全模板方法中缺失的调用逻辑或者要求写出子类重写某个抽象方法。备考的时候我最推荐的学习方式不是背二十三个设计模式的名称而是把“模板方法-工厂方法-策略模式”三个行为型模式放在一起对比着记。它们看起来差不多但用起来差别很大。我整理了一个简易对比表维度模板方法模式工厂方法模式策略模式关注点定义算法骨架子类填充步骤创建对象子类决定实例化哪个类算法族独立封装可互相替换实现手段继承重写继承重写组合委托变化点算法内部某一步骤创建对象的类型整个算法控制权父类控制流程子类填充细节父类定义创建逻辑子类确定具体产品客户端决定使用哪种策略典型例子AbstractList、JdbcTemplateCollection的iterator方法Comparator、排序算法7.2 面试中怎么回答模板方法模式面试官问“讲讲模板方法模式”只回答“父类定义方法骨架子类实现细节”只能拿及格分。想拿高分建议按这个思路组织回答第一说清楚它的结构抽象类、模板方法、抽象方法、钩子方法各自的作用。 第二说清楚它解决了什么问题复用固定流程、约束流程顺序、符合开闭原则。 第三说清楚它的适用场景多个子类共享相同的算法骨架但具体实现不同控制子类扩展的边界。 第四说清楚它的注意事项模板方法用final修饰防止被子类篡改钩子方法不要滥用继承层级不要过深。 第五最好能举一个源码或项目里的实例比如AbstractList或JdbcTemplate。面到源码举例基本就能证明你平时有过设计模式的阅读积累面试官那边的好感度会明显提高。8. 写在最后的经验之谈我在实际项目里用模板方法模式盯着一个原则先让代码重复到两遍以上才考虑抽模板。第一遍直接实现第二遍复制后改写第三遍出现的时候再顺手做抽象。过早抽象最大的问题在于你根本不知道哪些步骤是真的稳定的哪些只是“现在看起来稳定”。等到重复出现三次整个流程中哪一环在变、哪一环固定不变自然就看清楚了。这时候再提取父类、定义抽象方法、设计钩子准确率会高很多。很多设计模式教程喜欢拿“为什么能用”讲但真正吃透一个模式靠的是“什么时候不能用”。模板方法模式最典型的不适用场景就是流程变化本身也需要被扩展。举个例子如果你的订单流程今天是“校验、支付、发货”明天可能会变成“校验、支付、风控、发货”后天是“校验、优惠计算、支付、发货”——改动点是流程里步骤的增减而不是某一步的实现细节。这种情况强行用模板方法就只能靠钩子方法硬塞扩展点钩子一多代码就烂了。碰到这类场景我会换成责任链模式或状态机来组织流程让每一步作为独立节点动态组合。另外分享一个代码维护的小技巧在模板方法里不要把所有步骤都挤在一行里调用可以在每步之间加上简短注释说明这个步骤到底是固定逻辑、抽象逻辑还是钩子逻辑。我自己的习惯是public final void processOrder() { validateOrder(); // 固定订单基础校验 applyCoupon(); // 抽象子类实现各自的优惠策略 deductStock(); // 固定统一扣库存 doPayment(); // 抽象不同支付渠道 printReceipt(); // 钩子部分渠道需要打印小票 sendNotification(); // 固定通知用户 }这样哪怕是刚接手项目的新人看到这个模板方法也能立刻脑补出一条完整的业务执行链知道哪些环节不能动、哪些环节可以动、哪些环节可选。设计模式解决的是代码结构问题这种注释习惯解决的是代码的可读性问题两者配合才是真正好维护的代码。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号