1. 模板方法模式初探:面试官的"必问题"与程序员的"救星"
第一次听到"模板方法模式"这个名词时,我正坐在一家互联网公司的面试室里。对面的技术总监推了推眼镜,抛出一个看似简单的问题:"说说模板方法模式,以及你在项目中如何应用它?"当时的我支支吾吾,最终与这个offer失之交臂。后来才发现,这几乎是Java中高级面试中出现频率最高的设计模式之一。
模板方法模式(Template Method Pattern)属于行为型设计模式,它定义了一个操作中的算法骨架,而将某些步骤延迟到子类中实现。这种模式最大的价值在于:父类控制整体流程,子类实现具体细节。就像做菜的食谱模板——父类规定"洗菜→切菜→炒菜→装盘"的固定流程,而子类可以决定具体炒什么菜、用什么调料。
在实际开发中,模板方法模式随处可见:
- Spring框架中的JdbcTemplate
- Java Servlet中的doGet/doPost方法
- 各类工作流引擎的处理流程
- 企业级应用中的报表生成器
关键理解:模板方法不是用来实现具体功能的,而是用来定义功能执行的标准流程。这是面试中最容易混淆的点。
2. 深度解析:模板方法模式的双螺旋结构
2.1 模式结构解剖
标准的模板方法模式包含两个核心角色:
- 抽象模板(Abstract Class)
- 定义算法骨架(模板方法,通常用final修饰防止子类修改)
- 声明抽象方法(子类必须实现)
- 提供钩子方法(Hook Method,子类可选覆盖)
public abstract class ReportGenerator { // 模板方法(final防止篡改流程) public final void generateReport() { collectData(); analyzeData(); formatReport(); if(needExport()) { exportReport(); } } // 抽象方法(必须实现) protected abstract void collectData(); protected abstract void analyzeData(); // 具体方法(已有默认实现) protected void formatReport() { System.out.println("Formatting report in default style..."); } // 钩子方法(可选覆盖) protected boolean needExport() { return false; } protected void exportReport() { throw new UnsupportedOperationException(); } }- 具体实现(Concrete Class)
- 实现父类定义的抽象方法
- 选择性覆盖钩子方法
public class SalesReport extends ReportGenerator { @Override protected void collectData() { System.out.println("Collecting sales data from database..."); } @Override protected void analyzeData() { System.out.println("Analyzing sales trends..."); } @Override protected boolean needExport() { return true; } @Override protected void exportReport() { System.out.println("Exporting sales report to PDF..."); } }2.2 设计原理剖析
模板方法模式的核心在于好莱坞原则(Hollywood Principle):"Don't call us, we'll call you." 即父类掌控全局调用权,子类只需被动等待父类调用。这种控制反转(IoC)带来三大优势:
- 代码复用:将不变部分搬移到父类,避免重复代码
- 扩展可控:子类只能影响特定步骤,无法改变整体流程
- 便于维护:算法修改只需调整父类,不影响子类实现
在Spring的JdbcTemplate中,这种设计体现得淋漓尽致:
- 父类处理:获取连接、创建语句、关闭资源
- 子类实现:SQL编写、参数设置、结果处理
3. 实战演练:从电商系统看模板方法模式
3.1 支付流程标准化案例
假设我们要开发一个电商平台的支付模块,支持支付宝、微信、银联三种支付方式。传统写法可能是这样的:
// 反例:重复代码泛滥 class AlipayService { public void pay() { validate(); // 支付宝特有逻辑 checkAliAccount(); deductFromAli(); createAliTrade(); // 通用后置处理 sendNotification(); updateOrder(); } } class WechatPayService { public void pay() { validate(); // 微信特有逻辑 checkWechatAccount(); deductFromWechat(); createWechatTrade(); // 通用后置处理 sendNotification(); updateOrder(); } }使用模板方法模式重构后:
public abstract class PaymentService { // 模板方法 public final void processPayment() { validate(); deduct(); createTrade(); afterPay(); } protected void validate() { // 通用校验逻辑 } protected abstract void deduct(); protected abstract void createTrade(); protected void afterPay() { sendNotification(); updateOrder(); } } class AlipayService extends PaymentService { protected void deduct() { checkAliAccount(); deductFromAli(); } protected void createTrade() { createAliTrade(); } }3.2 性能监控模板实现
再来看一个更复杂的例子:为不同数据库操作添加性能监控。
public abstract class DatabaseOperationTemplate { public final Object execute() { long start = System.currentTimeMillis(); try { Object result = doOperation(); logSuccess(start); return result; } catch (Exception e) { logError(e, start); throw e; } } protected abstract Object doOperation(); private void logSuccess(long start) { long cost = System.currentTimeMillis() - start; System.out.printf("[SUCCESS] Operation %s cost %d ms\n", this.getClass().getSimpleName(), cost); } private void logError(Exception e, long start) { long cost = System.currentTimeMillis() - start; System.err.printf("[ERROR] Operation %s failed after %d ms: %s\n", this.getClass().getSimpleName(), cost, e.getMessage()); } } // 具体实现 class UserQuery extends DatabaseOperationTemplate { protected Object doOperation() { // 实际的用户查询逻辑 return userRepository.findAll(); } }4. 面试高频问题与避坑指南
4.1 常见面试题解析
模板方法模式与策略模式的区别?
- 模板方法:父类控制流程,子类实现细节(继承体系)
- 策略模式:完全替换算法实现(接口组合)
- 关键区别:是否要控制算法结构
为什么要用final修饰模板方法?
- 防止子类重写导致流程失控
- 保证算法骨架的稳定性
- 这是模板方法模式的核心约束
钩子方法的作用是什么?
- 提供扩展点,但不强制要求实现
- 例子:Spring的InitializingBean.afterPropertiesSet()
4.2 实际应用中的坑
过度抽象问题
- 反例:为只有一种实现的流程创建模板
- 建议:当发现重复流程时再考虑重构
流程僵化问题
// 错误示范:缺乏灵活性的模板 public abstract class BadTemplate { public final void process() { step1(); step2(); // 某些场景不需要这一步 step3(); } // ... }- 解决方案:引入更多钩子方法
public abstract class GoodTemplate { public final void process() { step1(); if(needStep2()) { step2(); } step3(); } protected boolean needStep2() { return true; } }多层级继承问题
- 避免创建超过两层的模板继承体系
- 复杂场景考虑结合策略模式
5. 模式进阶:模板方法在现代框架中的应用
5.1 Spring中的经典实现
JdbcTemplate是模板方法模式的教科书级案例:
public <T> T execute(ConnectionCallback<T> action) { Connection con = DataSourceUtils.getConnection(obtainDataSource()); try { // 前置处理 Connection conToUse = createConnectionProxy(con); // 核心操作交由回调实现 return action.doInConnection(conToUse); } catch (SQLException ex) { // 异常处理 throw translateException("ConnectionCallback", ex); } finally { // 后置清理 DataSourceUtils.releaseConnection(con, getDataSource()); } }这种设计实现了:
- 资源管理(连接获取/释放)的标准化
- 异常处理的统一封装
- 业务逻辑的灵活扩展
5.2 JUnit生命周期模板
JUnit 4的测试用例执行流程:
public abstract class TestCase { // 模板方法 public void runBare() throws Throwable { setUp(); try { runTest(); } finally { tearDown(); } } protected void setUp() {} protected void tearDown() {} protected void runTest() { // 反射调用测试方法 } }这种设计允许测试人员:
- 专注于测试方法编写(runTest)
- 按需重写前置/后置方法(setUp/tearDown)
6. 个人实践心得:什么时候该用模板方法模式?
经过多个项目的实践,我总结了模板方法模式的适用场景判断矩阵:
| 场景特征 | 适用模板方法 | 不适用模板方法 |
|---|---|---|
| 有清晰的算法骨架 | ✅ | ❌ |
| 多个实现有共同流程 | ✅ | ❌ |
| 需要严格控制执行顺序 | ✅ | ❌ |
| 各步骤实现差异很大 | ❌ | ✅ |
| 算法需要频繁整体替换 | ❌ | ✅(考虑策略模式) |
几个特别适合使用模板方法的典型场景:
- 具有固定步骤的文档生成(报表/合同)
- 数据导入导出流程
- 支付/交易处理流程
- 批处理任务执行框架
- 测试用例的生命周期管理
最后分享一个我实际项目中的技巧:当发现多个类中有相似流程时,先用注释写出理想模板结构,再逐步重构。例如:
// 重构前 class A { void process() { init(); // 相同 doA(); // 不同 cleanup(); // 相同 } } class B { void process() { init(); // 相同 doB(); // 不同 cleanup(); // 相同 } } // 重构后 abstract class Template { final void process() { init(); doProcess(); cleanup(); } abstract void doProcess(); }这种渐进式重构既能保证系统稳定,又能逐步提高代码质量。模板方法模式就像编程中的"食谱",掌握它,你就能在Java面试和实际开发中游刃有余。