设计模式-模板方法

模板方法模式

核心思想

在父类中定义算法的骨架(执行步骤和顺序),将某些步骤的具体实现延迟到子类。子类可以覆盖特定步骤,但不能改变整体流程结构。

这是一种基于继承的代码复用模式,解决的核心问题是:多个流程结构相同、但部分步骤实现不同的场景,如何消除重复代码。

结构

AbstractClass(抽象类)
  ├── templateMethod()   // final,定义算法骨架,不允许子类覆盖
  ├── step1()            // 具体方法,所有子类共用的逻辑
  ├── step2()            // abstract,子类必须实现
  └── hook()             // 钩子方法,子类可选择性覆盖,默认空实现

ConcreteClassA extends AbstractClass
  └── step2()            // 实现自己的版本

ConcreteClassB extends AbstractClass
  └── step2()            // 实现自己的版本

代码示例

数据导出场景为例:导出 Excel 和导出 CSV,流程相同(获取数据 → 转换格式 → 写入文件),只有转换和写入步骤不同。

抽象模板类:

public abstract class DataExporter {

    /**
     * 模板方法:定义导出流程,final 禁止子类覆盖整体流程
     */
    public final void export(String query) {
        List<Map<String, Object>> data = fetchData(query);  // 公共步骤
        String content = convert(data);                     // 抽象步骤,子类实现
        beforeWrite();                                      // 钩子,子类可选覆盖
        write(content);                                     // 抽象步骤,子类实现
        afterWrite();                                       // 钩子,子类可选覆盖
    }

    // 公共步骤:所有子类共用,直接在父类实现
    private List<Map<String, Object>> fetchData(String query) {
        System.out.println("执行查询: " + query);
        return List.of(Map.of("id", 1, "name", "张三"));
    }

    // 抽象步骤:子类必须提供具体实现
    protected abstract String convert(List<Map<String, Object>> data);

    protected abstract void write(String content);

    // 钩子方法:提供默认空实现,子类按需覆盖
    protected void beforeWrite() {}

    protected void afterWrite() {}
}

Excel 导出实现:

public class ExcelExporter extends DataExporter {

    @Override
    protected String convert(List<Map<String, Object>> data) {
        // 转换为 Excel 格式(示意)
        return "Excel内容: " + data;
    }

    @Override
    protected void write(String content) {
        System.out.println("写入 .xlsx 文件: " + content);
    }

    @Override
    protected void afterWrite() {
        System.out.println("发送导出完成邮件通知");
    }
}

CSV 导出实现:

public class CsvExporter extends DataExporter {

    @Override
    protected String convert(List<Map<String, Object>> data) {
        return "id,name\n1,张三";
    }

    @Override
    protected void write(String content) {
        System.out.println("写入 .csv 文件: " + content);
    }
}

调用:

DataExporter exporter = new ExcelExporter();
exporter.export("SELECT * FROM users");

// 执行查询: SELECT * FROM users
// 写入 .xlsx 文件: Excel内容: [{id=1, name=张三}]
// 发送导出完成邮件通知

钩子方法的作用

钩子方法(Hook)是模板方法中的关键设计——父类提供默认实现(通常为空),子类可以选择性覆盖,用来在流程的特定节点插入逻辑,而不影响整体骨架。

钩子还可以用于控制流程是否执行某步骤

public abstract class OrderProcessor {

    public final void process(Order order) {
        validate(order);
        pay(order);
        if (needsInvoice()) {   // 通过钩子控制是否开票
            generateInvoice(order);
        }
        notify(order);
    }

    protected abstract void pay(Order order);

    // 钩子:默认不开票,子类可覆盖
    protected boolean needsInvoice() {
        return false;
    }

    private void validate(Order order) { /* 公共验证逻辑 */ }
    private void notify(Order order) { /* 公共通知逻辑 */ }
    private void generateInvoice(Order order) { /* 开票 */ }
}

// 企业订单:需要开票
public class EnterpriseOrderProcessor extends OrderProcessor {

    @Override
    protected void pay(Order order) { /* 对公转账 */ }

    @Override
    protected boolean needsInvoice() {
        return true;
    }
}

Spring 中的模板方法

Spring 框架大量使用模板方法模式,最典型的例子:

JdbcTemplate:

// Spring 内部 execute() 是模板方法,定义了:获取连接 → 创建Statement → 执行 → 释放连接
// 用户只需通过 callback 提供变化部分
jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) -> {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setName(rs.getString("name"));
    return user;
});

AbstractList:

// AbstractList 实现了 Iterator、indexOf、lastIndexOf 等通用方法
// 子类只需实现 get(int index) 和 size() 两个抽象方法
public class MyList extends AbstractList<String> {
    private final String[] data;

    public MyList(String[] data) { this.data = data; }

    @Override
    public String get(int index) { return data[index]; }

    @Override
    public int size() { return data.length; }
}

HttpServlet:

// service() 是模板方法,根据请求类型路由到 doGet/doPost
// 子类只需覆盖对应方法
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        resp.getWriter().write("Hello");
    }
}

与策略模式的对比

模板方法和策略模式都能解决"同一流程、不同实现"的问题,区别在于扩展方式

对比项 模板方法模式 策略模式
实现机制 继承 组合
流程骨架 固定在父类,子类不可更改 无固定骨架,由 Context 控制
扩展方式 新增子类 新增策略实现类,注入 Context
运行时切换 不支持 支持动态切换策略
适用场景 流程固定,步骤实现不同 算法可互换,需运行时选择
代码耦合 子类与父类紧耦合 Context 与策略接口解耦

选择建议:

  • 流程步骤顺序固定,只是部分步骤实现不同 → 模板方法
  • 整个算法都可替换,或需要运行时动态切换 → 策略模式
  • 实际项目中两者常配合使用:模板方法定骨架,策略模式填充可变步骤

适用场景

  • 多个类有相同的执行流程,只有部分步骤实现不同(消除重复代码的首选)
  • 框架设计中,框架定义流程骨架,使用者实现具体步骤(如 Spring 各种 Template)
  • 需要控制子类扩展点,只允许子类修改特定步骤而不是整个流程
本文作者:
本文链接: https://hgnulb.github.io/blog/2026/45688018
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!