设计模式之模板模式(Template Pattern)详解

在软件开发中,我们常常遇到**“流程固定但部分步骤实现不同”**的场景:例如,泡茶和泡咖啡的流程都包含“煮水、冲泡、加调料”等步骤,但“冲泡的原料”和“添加的调料”因饮品而异;又如,框架中数据库操作的流程(建立连接→执行SQL→处理结果)固定,但“SQL语句”和“结果处理逻辑”因业务而异。

模板模式(Template Pattern)正是为解决这类问题而生的行为型设计模式。它的核心思想是:定义算法的骨架(模板方法),将可变步骤延迟到子类实现,从而在不改变流程结构的前提下,灵活扩展具体步骤。

目录#

  1. 模板模式的核心概念
  2. 模板模式的结构与角色
  3. 代码示例:从“泡茶”到“泡咖啡”
  4. 应用场景:框架、游戏与业务系统
  5. 优缺点分析
  6. 最佳实践:如何优雅使用模板模式?
  7. 与其他模式的对比(策略模式、工厂方法)
  8. 总结

1. 模板模式的核心概念#

模板模式的核心是:“固定流程骨架,扩展可变步骤”

  • 它定义一个算法的整体流程(模板方法),并将流程中可变的步骤抽象为方法,由子类实现。
  • 子类只需关注“可变步骤”的具体实现,无需关心整体流程,从而实现流程复用步骤扩展的平衡。

2. 模板模式的结构与角色#

模板模式包含3个核心角色:

(1)抽象模板类(Abstract Class)#

  • 定义模板方法(Template Method):封装算法的整体流程(如 prepareRecipe()),通常用 final 修饰(防止子类重写,保证流程稳定)。
  • 定义基本方法
    • 抽象方法(Abstract Method):流程中必须由子类实现的可变步骤(如 brew()addCondiments())。
    • 钩子方法(Hook Method):流程中可选扩展的步骤,通常提供默认实现(如 customerWantsCondiments()),子类可重写以控制流程分支。

(2)具体子类(Concrete Class)#

  • 继承抽象模板类,实现抽象方法重写钩子方法,完成可变步骤的定制。

(3)客户端(Client)#

  • 调用模板方法,触发算法流程。

3. 代码示例:从“泡茶”到“泡咖啡”#

我们用“制作热饮”的经典场景,演示模板模式的实现:

(1)抽象模板类:BeverageMaker#

// 抽象模板类:定义热饮制作的流程骨架
public abstract class BeverageMaker {
    // 模板方法:固定热饮制作的整体流程(final修饰,防止子类重写)
    public final void prepareRecipe() {
        boilWater();       // 固定步骤:煮水
        brew();            // 可变步骤:冲泡(子类实现)
        pourInCup();       // 固定步骤:倒入杯子
        if (customerWantsCondiments()) { // 钩子方法:是否加调料(可选扩展)
            addCondiments(); // 可变步骤:加调料(子类实现)
        }
    }
 
    // 固定步骤:煮水(所有热饮通用,父类实现)
    private void boilWater() {
        System.out.println("煮水...");
    }
 
    // 固定步骤:倒入杯子(所有热饮通用,父类实现)
    private void pourInCup() {
        System.out.println("倒入杯子...");
    }
 
    // 抽象方法:冲泡(子类必须实现)
    protected abstract void brew();
 
    // 抽象方法:加调料(子类必须实现)
    protected abstract void addCondiments();
 
    // 钩子方法:是否需要加调料?默认返回true(子类可重写)
    protected boolean customerWantsCondiments() {
        return true;
    }
}

(2)具体子类1:Tea(泡茶)#

public class Tea extends BeverageMaker {
    @Override
    protected void brew() {
        System.out.println("用沸水冲泡茶叶...");
    }
 
    @Override
    protected void addCondiments() {
        System.out.println("添加柠檬...");
    }
 
    // 重写钩子方法:当前用户不需要加调料
    @Override
    protected boolean customerWantsCondiments() {
        return false;
    }
}

(3)具体子类2:Coffee(泡咖啡)#

public class Coffee extends BeverageMaker {
    @Override
    protected void brew() {
        System.out.println("用沸水冲泡咖啡粉...");
    }
 
    @Override
    protected void addCondiments() {
        System.out.println("添加牛奶和糖...");
    }
 
    // 使用默认钩子方法(加调料)
}

(4)测试类:Client#

public class Client {
    public static void main(String[] args) {
        // 制作茶
        BeverageMaker tea = new Tea();
        tea.prepareRecipe();
        System.out.println("--- 分割线 ---");
        // 制作咖啡
        BeverageMaker coffee = new Coffee();
        coffee.prepareRecipe();
    }
}

输出结果:#

煮水...
用沸水冲泡茶叶...
倒入杯子...
--- 分割线 ---
煮水...
用沸水冲泡咖啡粉...
倒入杯子...
添加牛奶和糖...

代码解析:#

  • prepareRecipe()模板方法,固定了“煮水→冲泡→倒入→加调料(可选)”的流程。
  • brew()addCondiments()抽象方法,由子类(TeaCoffee)实现具体逻辑。
  • customerWantsCondiments()钩子方法Tea 重写后关闭了“加调料”步骤,Coffee 使用默认实现(加调料)。

4. 应用场景:框架、游戏与业务系统#

模板模式在以下场景中广泛应用:

(1)框架开发:流程固定,细节扩展#

  • Spring的JdbcTemplate:固定“获取连接→执行SQL→处理结果→关闭连接”的流程,“SQL语句”和“结果映射”由用户(子类/回调)实现。
  • Servlet的doGet/doPostservice() 是模板方法,固定“解析请求→调用doGet/doPost→生成响应”的流程,业务逻辑由 doGet()/doPost() 实现。

(2)游戏开发:角色创建流程#

  • 游戏中“创建角色”的流程:选择种族→选择职业→分配属性→生成外观。其中“分配属性”和“生成外观”因种族/职业而异,可用模板模式固定流程,子类(如 HumanWarriorCreatorElfMageCreator)实现可变步骤。

(3)业务系统:文档生成#

  • 报表生成工具:固定“读取数据→渲染模板→导出文件”的流程,“读取数据”(从DB/Excel)和“渲染模板”(PDF/Excel格式)由子类实现。

5. 优缺点分析#

优点:#

  1. 代码复用:模板方法固定流程,避免子类重复编写相同逻辑(如“煮水”“倒入杯子”)。
  2. 扩展性强:子类只需关注“可变步骤”,无需关心整体流程,灵活扩展。
  3. 开闭原则:对扩展开放(子类新增步骤实现),对修改关闭(模板方法的流程无需修改)。

缺点:#

  1. 耦合性高:抽象类与子类高度耦合,子类的修改可能影响整体流程。
  2. 维护成本:若流程步骤过多,抽象类会变得复杂,子类逻辑难以追踪。
  3. 灵活性限制:模板方法的流程是固定的,若需大幅调整流程,需修改抽象类(违反开闭原则)。

6. 最佳实践:如何优雅使用模板模式?#

(1)合理拆分步骤:抽象方法 + 钩子方法#

  • 抽象方法:用于流程中必须定制的核心步骤(如 brew())。
  • 钩子方法:用于流程中可选扩展的步骤(如 customerWantsCondiments()),提供默认实现,子类按需重写。

(2)模板方法用final修饰#

  • 模板方法(如 prepareRecipe())用 final 修饰,防止子类重写,破坏流程稳定性。

(3)避免“步骤爆炸”#

  • 若流程步骤过多(如超过5个可变步骤),需拆分抽象类,或结合策略模式封装部分步骤,降低复杂度。

(4)钩子方法的妙用#

  • 钩子方法可返回布尔值(如 isDebugMode())、对象(如 getConfig()),用于控制流程分支或传递配置。

7. 与其他模式的对比#

(1)模板模式 vs 策略模式#

  • 模板模式:固定流程骨架,扩展步骤的实现(如“冲泡用茶叶还是咖啡粉”)。
  • 策略模式:封装整个算法(如“排序用冒泡还是快排”),算法间可动态切换。
  • 场景选择:流程固定、仅步骤细节不同 → 用模板模式;需动态切换整个算法 → 用策略模式。

(2)模板模式 vs 工厂方法模式#

  • 工厂方法是模板模式的特殊情况:工厂方法的“创建对象”是模板方法中的一个步骤。
  • 例如:BeverageMakerbrew() 可设计为工厂方法,由子类决定创建“茶叶”还是“咖啡粉”对象。

8. 总结#

模板模式的核心是**“固定流程,扩展步骤”,适用于“流程稳定、细节可变”**的场景(如框架流程、业务模板)。

  • 优点:流程复用、扩展性强、符合开闭原则。
  • 缺点:耦合度高、维护成本高、灵活性有限。

使用时需注意:

  • 合理拆分抽象方法与钩子方法,避免步骤爆炸。
  • 结合策略模式、工厂方法等,补充模板模式的灵活性。

参考文献#

  1. 《设计模式:可复用面向对象软件的基础》(GoF经典著作)
  2. 菜鸟教程:模板模式
  3. InfoQ:《设计模式之模板方法模式的实践》

通过本文的解析与示例,希望你能掌握模板模式的核心思想,并在实际项目中灵活运用,让代码既稳定又可扩展!


作者:XXX
日期:YYYY-MM-DD
版权声明:本文原创,转载请注明出处。