Spring 全家桶的骨架是 Template Method——你天天在用,但从没意识到它是个设计模式 面试的时候被问到你用过哪些设计模式大部分 Java 开发者会答 Singleton、Factory、Strategy。很少有人提 Template Method。但翻开 Spring 的源码JdbcTemplate、RestTemplate、TransactionTemplate、RedisTemplate——每个都以 Template 结尾每个的核心都是同一个模式。你写的每一行jdbcTemplate.query()背后跑的都是 Template Method。不是这个模式冷门是它太自然了自然到你感觉不到它的存在。模式本身一句话能讲清Template Method 的定义很简单父类定义算法骨架子类实现具体步骤。用代码说更直接java public abstract class AbstractOrderProcessor { // 模板方法定义流程骨架final 防止子类改结构 public final void process(Order order) { validate(order); calculatePrice(order); save(order); notify(order); }protected abstract void validate(Order order); protected abstract void calculatePrice(Order order); // 可被子类覆盖的钩子 protected void save(Order order) { // 默认实现 database.save(order); } protected void notify(Order order) { // 默认实现 smsService.send(order.getPhone(), 订单已处理); }} process()是模板方法定义了校验→计价→保存→通知的流程顺序。子类只能改validate和calculatePrice的实现改不了流程顺序。这就是 Template Method 的全部。但简单的东西往往坑最多。Spring 怎么用的JdbcTemplate 的核心方法executejava public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { public T T execute(StatementCallbackT action) throws DataAccessException { Connection con DataSourceUtils.getConnection(getDataSource()); Statement stmt null; try { con.createStatement(); T result action.doInStatement(stmt); // 这一行是子类/回调实现的 return result; } catch (SQLException ex) { throw getExceptionTranslator().translate(StatementCallback, sql, ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } } }getConnection、createStatement、异常翻译、资源释放——这些固定步骤都在模板方法里。你只需要实现StatementCallback提供执行 SQL这一步。但注意一个细节Spring 这里其实做了变体。经典 GoF 的 Template Method 用继承子类覆盖抽象方法。Spring 用的是回调Callback组合替代继承。这不是 Spring 不懂 GoF是 Java 单继承的限制。如果 JdbcTemplate 用继承你的 DAO 必须继承 JdbcTemplate 才能用那还想不想继承别的类了回调模式让你在任意类里使用 JdbcTemplate代价是写一个匿名内部类或 Lambda。从 GoF 的角度看Spring 的做法是 Template Method Strategy 的混合体。骨架流程是 Template Method可替换步骤用 Strategy回调接口实现。理解这一点你就能理解为什么 Java 8 之后 Template Method 越来越不像模式了——Lambda 表达式让回调写起来太自然你甚至感觉不到自己在用模式。三个工程化坑坑一模板方法不加 finaljava public class OrderProcessor { public void process(Order order) { // 没有 final validate(order); calculatePrice(order); save(order); } }子类可以覆盖process()把流程顺序改了。我曾经在一个项目里看到有人这么干java public class SpecialOrderProcessor extends OrderProcessor { Override public void process(Order order) { save(order); // 先保存 validate(order); // 再校验 calculatePrice(order); } }先保存再校验。问为什么答业务要求先入库再校验。这不是 Template Method 的问题是业务流程设计的问题。但模板方法不加 final 给了这种错误可乘之机。GoF 原版就强调了模板方法应该是 final 的。流程骨架是契约不允许子类篡改。如果你想允许流程变化你应该用 Strategy 模式不是 Template Method。坑二抽象方法粒度太细java public abstract class AbstractReportGenerator { public final void generate() { loadHeader(); loadTitle(); loadSubtitle(); loadBody(); loadChart(); loadFooter(); loadPageNumber(); export(); }protected abstract void loadHeader(); protected abstract void loadTitle(); protected abstract void loadSubtitle(); protected abstract void loadBody(); protected abstract void loadChart(); protected abstract void loadFooter(); protected abstract void loadPageNumber(); protected abstract void export();} 8 个抽象方法子类要全部实现。问题是loadTitle和loadSubtitle在大部分子类里实现逻辑几乎一样都是从配置读取标题。你要么在每个子类里重复写要么提一个公共方法到父类——但提上去之后父类越来越胖变成了一个什么都有的上帝类。Template Method 的抽象方法应该对应真正的变化点不是对流程做最细粒度的拆分。如果某个步骤在大部分子类里都一样它就不该是 abstract 的应该是一个带默认实现的普通方法或 hook。原则抽象方法的数量跟子类数量成反比。子类多抽象方法要少子类少抽象方法可以多。5 个以上子类还用 8 个抽象方法的 Template Method维护成本会爆炸。坑三跟 Strategy 搞混这是最常见的问题。很多人分不清什么时候用 Template Method什么时候用 Strategy。判断标准很简单如果变化点是步骤的实现方式用 Template Method。流程顺序固定只是每步的具体做法不同。如果变化点是整个算法用 Strategy。流程本身都可能变不只是步骤实现。举个例子。订单处理流程校验→计价→保存→通知。不管什么类型的订单这四步的顺序不会变只是每步的实现不同普通订单和秒杀订单的校验逻辑不同。这是 Template Method。支付方式选择微信支付、支付宝支付、银行卡支付。每种支付的整个流程都不同微信支付要调 JSAPI支付宝要调 SDK银行卡要走银联通道。这是 Strategy。混在一起的典型表现你写了一个 AbstractPaymentProcessor里面有prepare()、pay()、callback()三个抽象方法但微信支付的prepare()里有 5 步操作银行卡支付的prepare()里有 2 步操作流程完全不同。这时候你已经不是在用 Template Method 了你是在用继承模拟 Strategy代码会很别扭。现代 Java 里的 Template MethodJava 8 之后Template Method 有了更轻量的写法。不用继承用函数式接口java public class HttpClient { public T execute(Request request, Function handler) { Response response doRequest(request); // 固定步骤 T result handler.apply(response); // 可变步骤 cleanup(); // 固定步骤 return result; } }// 使用 client.execute(request, resp - parseJson(resp.getBody())); 这种写法本质上还是 Template Method——骨架流程固定可变步骤通过参数传入。但不需要继承不需要抽象类一个 Lambda 搞定。Spring 的 JdbcTemplate 在 Java 8 之后也做了类似改造大量使用 Lambda 替代匿名内部类。模式没变变的是实现方式。什么时候该用Template Method 适合的场景有一个共同特征你能在多个场景中看到相同的流程顺序不同的步骤实现。具体来说报表生成加载数据→格式化→导出。不同报表的数据源和格式化逻辑不同但流程一样。数据迁移读取→转换→写入。不同数据源的读取和写入不同但流程一样。消息处理接收→解析→校验→处理→确认。不同消息类型的处理逻辑不同但流程一样。生命周期管理初始化→启动→运行→停止→销毁。Servlet、Spring Bean、线程池都是这个流程。如果你发现自己在多个类里写了相同的 try-finally 结构相同的调用顺序相同的异常处理模板——那就是 Template Method 在向你招手。反过来如果流程顺序本身需要变化或者步骤数量不固定Strategy 或 Chain of Responsibility 更合适。Template Method 的前提是流程骨架稳定变化的只是步骤内容。我在做一个用卡皮巴拉讲设计模式的微信小程序「爪爪代码冒险记」23 个设计模式用漫画 答题的方式讲目前正在开发中。如果你觉得这类内容有意思搜一下「爪爪代码冒险记」或者等我后面的文章。