Skip to content

模板方法

模板方法模式是软件设计模式中行为型模式的一种,它通过继承来实现代码复用和流程控制,是一种非常实用且常见的设计模式。

核心思想

定义一个算法的骨架(模板),而将一些步骤延迟到子类中实现。

  • 父类(抽象类)中定义了一个完整的、固定的执行流程
  • 这个流程中的某些具体步骤是抽象的,由子类去实现。
  • 子类可以在不改变算法整体结构的情况下,重新定义(重写)这些步骤。

换句话说这就像一个“烹饪食谱模板”:

  • 食谱规定了步骤:准备食材 → 烹饪 → 装盘 → 上菜。
  • 但“烹饪”这一步,可以是“炒”、“煮”、“烤”,由不同的厨师(子类)决定。

主要角色

角色说明
1. 抽象类(Abstract Class)定义算法的骨架(模板方法),包含抽象方法和具体方法。模板方法通常是 final 的,防止被子类修改。
2. 模板方法(Template Method)在抽象类中定义的 final 方法,它描述了算法的框架,调用其他抽象方法或具体方法。
3. 抽象方法(Abstract Methods)在抽象类中声明但没有实现的方法,必须由子类实现。代表算法中可变的部分。
4. 具体方法(Concrete Methods)在抽象类中已经实现的方法,子类可以直接继承使用。代表算法中通用的、不变的部分。
5. 钩子方法(Hook Methods)在抽象类中实现的空方法或默认实现的方法,子类可以选择性地覆盖。用于“挂钩”到流程中,控制流程或添加可选行为。
6. 具体子类(Concrete Class)继承抽象类,并实现其中的抽象方法。可以覆盖钩子方法。

适用场景

  • 多个子类有相同的算法结构,只是某些步骤不同。
  • 把公共行为提取到父类,避免代码重复。
  • 控制子类的扩展,只允许在特定点进行定制。
  • 一次性实现算法的不变部分,并留出可变部分给子类实现。

例子:

java
public abstract class CaffeineBeverage {
    
    // 模板方法:定义了制作热饮的完整流程
    // 用 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; // 默认加调料
    }
}
java
// 制作咖啡
public class Coffee extends CaffeineBeverage {
    @Override
    protected void brew() {
        System.out.println("用沸水冲泡咖啡");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加入糖和牛奶");
    }
}

// 制作茶
public class Tea extends CaffeineBeverage {
    @Override
    protected void brew() {
        System.out.println("用沸水浸泡茶叶");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加入柠檬");
    }

    // 覆盖钩子方法:茶不加调料
    @Override
    protected boolean customerWantsCondiments() {
        return false;
    }
}
java
public class Client {
    public static void main(String[] args) {
        CaffeineBeverage coffee = new Coffee();
        coffee.prepareRecipe();
        // 输出:
        // 将水煮沸
        // 用沸水冲泡咖啡
        // 倒入杯中
        // 加入糖和牛奶

        System.out.println("--------");

        CaffeineBeverage tea = new Tea();
        tea.prepareRecipe();
        // 输出:
        // 将水煮沸
        // 用沸水浸泡茶叶
        // 倒入杯中
        // (没有加调料,因为钩子返回 false)
    }
}
最近更新