说到Java设计模式里的模板方法模式,我第一时间想到了西游记里的天条。天庭规矩多如牛毛,但每一次处罚流程其实都藏在固定的套路里:先是发现问题、立案奏报,然后玉帝准奏、宣旨传令,再由神将执行,最后定刑善后。流程是固定的,每一步具体怎么执行,则各有各的招。这个“固定流程+可变细节”的思路,就是模板方法模式(Template Method Pattern)的核心,也是这一回要重点聊透的东西。
模板方法模式把一个业务算法的骨架定义在父类里,把某些步骤延迟到子类去实现。子类不能改动骨架,只能填上自己的细节。听起来是不是像极了天条?规矩定死了,执行却可以各显神通。这篇文章是《Java设计模式西游篇》系列的第十回,我会先用西游记里的故事把这模式讲明白,再带你写一套Java代码落地,然后看看Servlet、Spring那些框架里藏着的模板方法,最后聊一聊模板方法与策略模式怎么取舍,以及我在真实项目里踩过的那些坑。
如果你刚开始学设计模式,也不用担心,模板方法模式几乎是入门门槛最低的几个模式之一,不需要你掌握多复杂的机制,只要理解继承和多态,就能看懂全部内容。准备面Java开发岗的话,这一回也值得认真读,模板方法与策略模式的区别、模板方法在框架中的应用,都是面试里出现频率不低的高频题。
1. 天条定流程:模板方法背后的西游智慧
1.1 天庭的“标准处罚流程”是怎么运作的
看过西游记的朋友应该都有印象,天庭处理犯错的神仙妖怪,有一套固定的章程。大致是五步:第一步,由纠察灵官发现问题并上奏;第二步,玉帝准奏,正式立案;第三步,相关神仙宣旨传令;第四步,执行神将动手处置;第五步,根据罪行轻重善后——轻则贬下凡间,重则压在山下或者送斩妖台。
这五步的顺序是雷打不动的,不会因为犯错者的身份、地位、罪名不同而变化。但具体到每一步的内容,那真是千差万别。同样是“贬下凡间”,卷帘大将是被打落流沙河,天蓬元帅是投了猪胎去福陵山,位置不同、形态不同、后续命运也不同。同样是“压在山下”,孙悟空被五行山压住,还贴了“唵嘛呢叭咪吽”六字真言,别的妖怪未必有这个待遇。
放到设计模式里,这套天庭的章程就是模板方法模式。它定义一个算法(或者说业务流程)的骨架,把其中一些步骤交给不同的“子类”去实现。骨架不能改,细节随便换。故事里的天条是骨架,处罚是流程,而各位神仙如何执法、为何这么执法,全部由“子类”说了算。
很多人刚接触“Template Method”这个词时,会把它理解偏。它其实说的是两件事:一个“模板”,一个“方法”。模板指的是父类里写好的那个对外公开的方法,这个方法内部把业务执行的顺序安排得明明白白,中间有些步骤自己实现,有些步骤留给子类。这个方法就是模板方法。子类可以重写方法里的某些步骤,但永远不能改动这个方法本身——就像天条规定了流程,任何执法神仙都不可能跳过“宣旨”直接“行刑”。
1.2 孙悟空大闹天宫:一场教科书级的模板方法实践
咱们拿“大闹天宫”这一整段故事来仔细拆解。天庭整个“讨伐妖猴”的流程,可以抽象成这样一个模板方法:
- 发布讨伐令,把妖猴的罪名昭告三界;
- 派兵出战,先后上了巨灵神、哪吒、二郎神;
- 如果失利就升级方案,换更强的神将、用法宝;
- 战斗结束,押解法场;
- 定罪并执行惩罚,走斩妖台、八卦炉,最后到五行山。
这个过程放在代码里,就是一个个步骤方法。天庭是那个抽象类,它把这五个步骤按顺序写死,但每一步的方法实现在不同的“讨伐行动”里完全不同。第一次讨伐派的是巨灵神,第二次是哪吒,第三次请的是二郎神。用Java写出来,就是一个AbstractTaofa类,里面定义execute()模板方法,依次调用publishOrder()、sendArmy()、escalate()、capture()、punish()。其中这个sendArmy()、punish(),就延迟到具体子类里实现。
这段故事里还藏着一个关键点:模板方法本身应该尽量做成final,或者至少不允许子类轻易覆盖。这和“天条戒律不可违”简直是一个意思。如果你允许子类随随便便把整个流程改掉,那模板方法的骨架约束就失去了意义。父类控制流程,子类填充细节,这两件事必须同时成立,才算真正用好了模板方法模式。
1.3 为什么固定流程更好:代码回归与团队秩序
我承认,固定流程这四个字听起来确实不够“灵活”,甚至有人会觉得它死板。但模板方法模式要的恰恰就是这个“死板”。业务里一旦有大量操作都遵循同一个大流程,固定的骨架带来的好处非常明显。
第一是一致性。所有子类跑的都是同一套流程,不会出现A模块的流程跟B模块的流程差得离谱的情况,行为可预期,排查问题也方便。第二是复用性。公共逻辑比如日志记录、资源释放、异常处理,集中在父类里,子类不需要重复编写。第三是约束性。用final锁死模板方法,从编译期就杜绝了子类误改主流程的可能。这种“强制性规范”在团队协作里太重要了。
你可以试着想象一下:如果每个执法神仙都自由发挥、随便改流程,有的先打再宣旨,有的先宣旨再打,天界秩序早就乱成一锅粥了。代码团队也一样,如果有人为了应付自己的业务场景,悄悄把公共流程的顺序调整了,后面接手的人一定会一脸茫然。模板方法模式要的,就是这个“天条戒律不可违”的效果,这也是它在所有行为型设计模式里最容易被低估的价值。
2. 模板方法模式的结构与Java实现
2.1 角色拆解:抽象类、模板方法与具体子类
模板方法模式的结构并不复杂,核心就三个角色。
第一个是抽象类(AbstractClass),它负责定义整套算法的骨架,里面有一个模板方法和若干步骤方法。第二个是模板方法(Template Method),它在抽象类中实现,规定流程的执行顺序,一般会标记为final。第三个是具体子类(ConcreteClass),它继承抽象类,实现那些延迟到子类的步骤方法。
步骤方法还可以细分成三类:抽象方法、具体方法、钩子方法。抽象方法必须由子类实现,父类只给签名,不给逻辑;具体方法是父类自己的默认实现,子类可以选择性覆盖;钩子方法则是父类里留出来的扩展点,默认是一个空实现或者返回一个默认值,子类需要的时候可以去重写它。
用文字来描述这个类图,大概是这样的:抽象类在顶层,放着模板方法templateMethod(),它内部按顺序调用stepA()、stepB()、hook()。stepA()是抽象方法,stepB()是具体方法,hook()是钩子方法,默认空实现。底下的具体子类ConcreteClassA和ConcreteClassB分别实现stepA(),有可能覆盖stepB()或者hook()。整套下来,“流程固定、细节自由”的目标就达成了。
2.2 代码示例:从“渡劫”到“取经”的通用流程
直接上代码吧。我挑了一个西游记里很常见的场景:渡劫。假设同一个程序要处理凡人渡劫、妖怪渡劫、神仙下凡渡劫三种情况,它们的流程一样,都是“准备—引雷—渡劫—处理结果—善后”,但每一步的具体内容完全不同。
先定义一个抽象类DuJieTemplate:
/** * 渡劫模板:规定渡劫的整套流程,子类不得修改流程本身 */ public abstract class DuJieTemplate { /** * 模板方法:定义渡劫流程 * final 防止子类改变流程顺序 */ public final void duJie() { System.out.println("=== 渡劫流程开始 ==="); prepare(); // 第一步:准备 triggerLei(); // 第二步:引发天劫 if (needExtraStrength()) { // 钩子:是否需要额外强化 extraStrength(); // 可选步骤 } handleOutcome(); // 第三步:处理渡劫结果 aftercare(); // 第四步:善后 System.out.println("=== 渡劫流程结束 ==="); } /** 准备阶段,所有渡劫者各自准备 */ protected abstract void prepare(); /** 引发天劫:有的渡劫者方式不同,父类给一个默认实现 */ protected void triggerLei() { System.out.println("天雷降下,轰轰作响"); } /** 钩子方法:默认不需要额外强化 */ protected boolean needExtraStrength() { return false; } /** 额外强化,钩子方法触发后执行 */ protected void extraStrength() { System.out.println("服用丹药,强行提升实力"); } /** 处理渡劫结果:有人成功有人失败 */ protected abstract void handleOutcome(); /** 善后:默认什么也不做,子类可以覆盖 */ protected void aftercare() { System.out.println("渡劫完毕,尘埃落定"); } }然后是两个具体子类。一个是普通修仙者,一个是妖王:
/** * 普通修仙者渡劫 */ public class NormalCultivatorDuJie extends DuJieTemplate { @Override protected void prepare() { System.out.println("准备打坐,稳定心神"); } @Override protected void handleOutcome() { System.out.println("渡劫成功,修为突破"); } @Override protected void aftercare() { System.out.println("闭关巩固境界,三日之后出关"); } } /** * 妖王渡劫 */ public class DemonKingDuJie extends DuJieTemplate { @Override protected void prepare() { System.out.println("召集小妖,布置护山大阵"); } @Override protected void triggerLei() { System.out.println("天雷被护山大阵削弱,威力小了一半"); } @Override protected boolean needExtraStrength() { return true; } @Override protected void extraStrength() { System.out.println("吞下千年妖丹,妖气暴涨"); } @Override protected void handleOutcome() { System.out.println("妖王硬扛天雷,渡劫成功,化形为半妖半仙之体"); } @Override protected void aftercare() { System.out.println("打开宝库,犒赏群妖"); } }调用主流程:
public class Main { public static void main(String[] args) { System.out.println("----- 普通修仙者渡劫 -----"); DuJieTemplate normal = new NormalCultivatorDuJie(); normal.duJie(); System.out.println(); System.out.println("----- 妖王渡劫 -----"); DuJieTemplate demon = new DemonKingDuJie(); demon.duJie(); } }运行结果:
----- 普通修仙者渡劫 ----- === 渡劫流程开始 === 准备打坐,稳定心神 天雷降下,轰轰作响 渡劫成功,修为突破 闭关巩固境界,三日之后出关 === 渡劫流程结束 === ----- 妖王渡劫 ----- === 渡劫流程开始 === 召集小妖,布置护山大阵 天雷被护山大阵削弱,威力小了一半 吞下千年妖丹,妖气暴涨 妖王硬扛天雷,渡劫成功,化形为半妖半仙之体 打开宝库,犒赏群妖 === 渡劫流程结束 ===两个子类的流程顺序完全一致,但每一步的内容全不相同。普通修仙者不会触发“吞妖丹”那一步,妖王却触发了。这里其实有两个钩子方法在起作用:needExtraStrength()判断是否需要强化,extraStrength()执行强化。父类完全不关心子类怎么决定,只负责在流程里问一句“你需不需要”,需要就调用,不需要就跳过。
我在写这段代码时做了几个设计选择,给你说说背后的考虑。
第一,duJie()方法加final。在团队开发里,这个final相当重要。很多新手会把模板方法当成普通方法,在子类里直接整个重写,把流程改得面目全非。加上final,编译期就拦住了。
第二,钩子方法我用了“判断”和“执行”两个方法配合。这是模板方法模式里非常常见的写法,一个方法负责返回布尔值决定是否执行,另一个方法负责真正干活。父类只负责在流程中调用它们,至于什么条件下执行,由子类自己定。
第三,triggerLei()在父类给了默认实现。能给出合理默认值的步骤,尽量不要定义成抽象方法。如果所有步骤都强制子类实现,子类里会充满“空实现”和“只是为了满足语法”的方法,这种坏味道会严重影响代码的可读性。
2.3 钩子方法:给天条留一扇灵活的门
钩子方法(Hook Method)是模板方法模式里最容易被忽视但至关重要的概念。它就是一个在模板方法中被调用、但默认不做任何事或者返回默认值的方法,子类可以根据需要去覆盖它。为什么需要它?因为不是所有步骤在任何场景下都必须执行。
回到渡劫的例子。不是每个渡劫者都要临时吞丹药强化,但父类又需要把“是否强化”这个判断留到运行时才能确定。钩子方法就是那个“运行时问询点”:父类问子类“你需要强化吗”,子类根据自身情况返回true或false,然后父类决定是否调用对应的方法。
钩子方法最常见的两个用途,一个是控制流程中某些步骤是否执行,另一个是在流程的某个固定节点插入额外逻辑。比如报表导出流程,父类固定了“查询数据—填充模板—输出文件”,有的子类只想查数据不想填充,钩子方法fillReport()默认返回false,子类覆盖成true才执行填充。又比如框架会在关键节点调用onStart()、onEnd()这类钩子,子类覆盖后加上日志、统计等操作,不影响主流程。
钩子方法的设计原则有一条必须守住:默认行为必须无害。如果你设计的钩子方法默认就会改变流程走向,那它就是不合格的。优秀的开源框架里钩子方法密密麻麻,但你去看它们的默认实现,基本全是空方法或者返回false,目的就是让大多数子类不重写也能正常运行。
3. 模板方法在Java框架中的身影
3.1 Servlet里的模板方法:doGet与doPost
Java开发者第一次接触模板方法模式,大概率是在Servlet里。HttpServlet类定义了一个完整的请求处理模板方法service(),它会先看HTTP方法,是GET就调doGet(),是POST就调doPost(),是PUT就调doPut()。我们写Servlet时,都是继承HttpServlet,只重写doGet()和doPost(),根本不用管它是怎么判断HTTP方法的。
service()就是那个模板方法,它内部的流程是固定的:获取请求方法、分发到对应的doXxx方法、输出响应。这个流程不需要修改,业务逻辑在doGet()和doPost()里由我们实现。而且doGet()、doPost()的默认实现是直接抛异常的,这逼着你必须去重写,和“抽象方法必须由子类实现”是一个道理。
简化看,service()的逻辑大致长这样:
// 简化示意:HttpServlet.service() 的分发逻辑 protected void service(HttpServletRequest req, HttpServletResponse resp) { String method = req.getMethod(); if (method.equals("GET")) { doGet(req, resp); } else if (method.equals("POST")) { doPost(req, resp); } else if (method.equals("PUT")) { doPut(req, resp); } // ... 其他方法分发 }从Servlet这个例子能看出,模板方法模式在框架设计中几乎无处不在。框架作者不可能预知每个使用者的业务逻辑,于是把流程骨架和扩展点定好,把具体实现留给你。这也是模板方法模式在框架层面最大的价值:使用者通过继承子类、实现特定方法就能扩展框架,同时不需要了解框架的完整执行细节。
3.2 Spring中的模板方法:JdbcTemplate与RestTemplate
Spring框架里有一堆以“Template”结尾的类,JdbcTemplate、RestTemplate、JmsTemplate,它们本质上都是模板方法思想的应用。有些是纯粹的模板方法模式,有些混用了回调模式,但“骨架固定、细节可变”的内核是一致的。
拿JdbcTemplate举例。它的execute()方法定义了一套执行JDBC操作的固定步骤:获取连接、创建Statement、执行SQL、处理结果、关闭连接、释放资源。作为业务开发者,你不需要写那套获取连接、关闭连接的老代码,只要提供一个StatementCallback回调,告诉它“执行什么SQL、怎么处理结果”。
简化示意如下:
// 简化示意:JdbcTemplate.execute() 的固定步骤 public <T> T execute(StatementCallback<T> action) { Connection conn = getConnection(); // 1. 获取连接 Statement stmt = conn.createStatement(); // 2. 创建语句 try { return action.doInStatement(stmt); // 3. 执行业务回调 } finally { close(stmt); // 4. 关闭语句 close(conn); // 5. 释放连接 } }RestTemplate也类似,它封装了HTTP请求的固定流程:建立连接、设置请求头、发送请求、读取响应、解析结果。你只需要指定URL、参数和想要的返回类型,具体流程由模板类完成。你通过回调定制的是“处理响应”那一步,其他全交给模板。
这里有一个细节值得注意:Spring的JdbcTemplate用的是回调加模板方法,而不是让使用者继承模板类。原因是Java是单继承,如果你让使用者的类去继承JdbcTemplate的子类,那这个类就无法再继承自己的业务基类了。用回调可以避免继承绑定,比单纯用继承更灵活。面试时聊到JdbcTemplate,能说出“它是基于模板方法思想实现的,并用回调来解耦”,层次感就出来了。
3.3 手写迷你框架:用模板方法实现一个天条引擎
光看不练还是不够。我带你手写一个精简的“天条发布引擎”,模拟天庭发布天条、执行天条的全流程。这个例子不复杂,但能让你看到模板方法在框架层面的建模思路。
假设天条发布流程分五步:起草天条、审核天条、发布公告、执行天条、记录存档。“审核”这一步,不同类别的天条不一样;“执行”这一步,有的是降灾、有的是赐福,差别更大。“发布公告”和“记录存档”的逻辑基本一致,父类直接提供默认实现。
/** * 天条发布引擎:固定天条发布的流程 */ public abstract class TianTiaoEngine { // 模板方法:定义天条发布流程,final 防止子类篡改 public final void publish() { try { draft(); // 1. 起草 review(); // 2. 审核 announce(); // 3. 发布公告 execute(); // 4. 执行 } finally { archive(); // 5. 记录存档,无论如何都要执行 } } protected void draft() { System.out.println("灵官起草天条草案"); } /** 审核逻辑因天条类别而异 */ protected abstract void review(); protected void announce() { System.out.println("通明殿前敲响天鼓,昭告三界"); } /** 执行逻辑因天条内容而异 */ protected abstract void execute(); protected void archive() { System.out.println("天条存入藏经阁,记录在案"); } }两个子类,一个降灾、一个赐福:
/** * 降灾天条 */ public class DisasterTianTiao extends TianTiaoEngine { @Override protected void review() { System.out.println("太白金星审核:确认受灾地区为该受罚之处"); } @Override protected void execute() { System.out.println("雷部行刑,降下三千里雷灾"); } } /** * 赐福天条 */ public class BlessingTianTiao extends TianTiaoEngine { @Override protected void review() { System.out.println("月老审核:确认赐福对象符合条件"); } @Override protected void execute() { System.out.println("福德星君赐福,降下十年风调雨顺"); } }运行一下:
TianTiaoEngine tianTiao = new DisasterTianTiao(); tianTiao.publish();输出:
灵官起草天条草案 太白金星审核:确认受灾地区为该受罚之处 通明殿前敲响天鼓,昭告三界 雷部行刑,降下三千里雷灾 天条存入藏经阁,记录在案无论降灾还是赐福,整个天条发布流程都被锁在这五步里,不会出现“没审核就执行”这种漏洞。模板方法模式在框架设计中的定位,就是通过父类定义不可更改的流程,保证核心业务规则不被破坏。注意我在这里用了一个try-finally来包住流程,这样“记录存档”无论如何都会执行,这在生产环境里是救命级别的细节。
4. 模板方法 vs 策略模式:你该用哪个?
4.1 一句话记住两者的核心区别
面试里被问到“模板方法模式和策略模式有什么区别”的次数,可能仅次于“说说单例模式的实现方式”。很多人的回答含含糊糊,我建议你用一句话记牢:
模板方法是“骨架统一、细节可变”,策略模式是“整体可替换、结果各不相同”。
展开来说,区别在三个层面。
第一是关系层面。模板方法模式基于继承,抽象类定义算法骨架,子类实现可变步骤,子类和父类是“is-a”的关系。策略模式基于组合,上下文(Context)持有一个策略对象的引用,在运行时可以动态切换,是“has-a”的关系。
第二是变化粒度。模板方法改的是算法内部的某个步骤,策略模式换的是整个算法。模板方法没法在运行时替换这个模板方法本身,策略模式却可以在运行时把整个策略对象换成另一个。
第三是灵活度。模板方法的灵活性是“受控的灵活”,策略模式是“完全的自由”。前者的约束有利于团队协作,后者的自由有利于快速扩展和切换。
用一个生活场景来对比:把“去西天取经”看作一条流程,模板方法模式就像观音菩萨给定了一条取经路线,从长安出发、途经五行山、收徒弟、过火焰山、抵达灵山,顺序不能变,但每一步怎么走可以自己安排。策略模式则是你今天可以选择骑马,明天可以选择坐筋斗云,整个“出行方式”作为一个整体可以替换。
两种模式不但不冲突,还经常配合使用。比如模板方法流程里的某一步,完全可以用策略模式来动态选择具体实现。我之前做过一次消息推送功能,推送流程用了模板方法:“校验参数—构建内容—调用推送渠道—记录日志”,而“调用推送渠道”这一步用了策略模式,在短信、邮件、钉钉机器人之间动态切换。这种“模板方法定骨架,策略模式换细节”的组合方式非常实用。
4.2 选型指南与应用场景对比
那到底什么时候用模板方法,什么时候用策略模式?我按自己在项目里的选型经验,给你几条判断标准。
优先考虑模板方法模式的情况有三类。第一,多个子模块处理的是同一个业务流程,流程顺序稳定,只有局部实现不同。第二,需要在流程前后统一做资源管理、日志记录、事务控制等公共逻辑。第三,希望约束团队成员的实现,避免有人篡改主流程。
典型场景包括:数据导出(查询数据—转换格式—输出文件)、数据校验(读取数据—逐项校验—汇总结果)、日志处理(采集—过滤—格式化—写入)、测试框架的用例执行流程(初始化—执行用例—清理环境)等。
优先考虑策略模式的情况也有三类。第一,同一功能有多种完整的算法,而且算法之间可以完全替换。第二,需要在运行时动态切换算法。第三,希望通过组合替代继承,避免类层次深度绑定。
典型场景包括:支付渠道(支付宝、微信、银行卡随时切换)、排序算法、促销计费方式、压缩算法选择等。
如果你一下子拿不准,可以问自己一个问题:我要变化的到底是“流程中的某一个步骤”,还是“整个算法方案”?前者用模板方法,后者用策略模式。这个问题想清楚了,基本就不会选错。
5. 常见问题与避坑指南
5.1 问题速查表:从“重写模板方法”到“子类爆炸”
我在做代码评审和带新人的过程中,见过不少模板方法模式的误用。把常见问题整理成一张速查表,你对照自查一下,看自己有没有踩过同样的坑。
| 问题现象 | 背后的原因 | 建议做法 |
|---|---|---|
| 子类直接覆盖了整个模板方法 | 没理解final约束的意义 | 模板方法加final修饰,代码评审时重点检查 |
| 抽象方法过多,子类大量空实现 | 父类没有为步骤提供默认行为 | 能给出默认值的步骤不要设计成抽象方法 |
| 模板方法内部越来越臃肿 | 父类承担了不属于算法骨架的职责 | 把非核心步骤拆出去,或拆成多个模板方法 |
| 子类层级过深,继承体系庞大 | 为了复用代码过度使用模板方法 | 考虑用组合(回调、策略)替代继承 |
| 钩子方法命名不清,无法判断作用 | 钩子方法命名不规范 | 用need、has、should等开头表达判断语义 |
| 流程某一步抛异常后,公共清理逻辑没执行 | 模板方法内部没做异常兜底 | 用try-finally保证资源释放、日志记录必然执行 |
最后一行值得多说几句。模板方法模式看似只是定义了一个流程方法,可如果这个方法内部没有做好异常兜底,中间任何一步抛异常,整个流程就断了,公共清理逻辑也执行不到。我写这类代码时有个习惯,就是在模板方法里用try-finally把流程包起来,保证“无论结局如何,存档和清理都要做”。这种细节看起来不起眼,但在生产环境里,一次流程现场有没有完整记录,对排查问题可能起着决定作用。
5.2 实战心得:我在项目里用模板方法踩过的坑
再说几个我自己的实操经验。第一,模板方法模式不要拿来过度设计。我见过有人给一个只有两步操作的小流程硬套模板方法,抽象类、具体子类、钩子方法写了一大堆,结果代码量翻了一倍,可读性反而下降了。这套模式适合用在“流程足够稳定、步骤足够多、多个子类确实有公共处理逻辑”的场景。如果业务就两三步,而且只有一个实现,老老实实写在一个类里就好。
第二,在设计抽象类时,一定要把“不变的”和“会变的”分清楚。我个人的习惯是,动手写代码之前先画一遍流程,把那些在所有场景下都一样的步骤标成具体方法,把那些在不同子类中表现不同的步骤标成抽象方法,把那些“偶尔会执行”或“留一个扩展位”的步骤设计成钩子方法。先理清这个分类再写代码,比边写边想清晰得多,后期也不会频繁改抽象类的接口。
第三,模板方法一定要写好文档注释。父类里的模板方法是整个算法的核心,如果不写清楚它的调用顺序、每个步骤的职责、子类要实现什么,后面的维护者真的会一头雾水。我一般会在抽象类上用Javadoc写明模板方法每一步的用途,以及哪些方法必须重写、哪些可选覆盖。相信我,三个月后你回头看这段代码,一定会感谢当时多写了几行注释的自己。
就我个人经验来说,模板方法模式是整套Java设计模式里性价比很高的一种。它不像观察者模式那样要处理复杂的事件注册机制,也不像代理模式那样要面对各种动态生成的字节码,理解门槛低,用起来收益却很直接。我最常用的地方就是给团队立流程:把“必须一致的部分”锁死在父类里,把“可以自由发挥的部分”交给子类,这样多人协作时代码风格自然就统一了。如果你也在为流程容易被改乱、公共逻辑散落各处而头疼,不妨给代码立一套属于你的“天条”。