什么是模板方法模式?

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义一个操作的算法骨架(模板方法),将算法的一些步骤延迟到子类中实现。模板方法确保算法的结构不变,而子类可以按需重写具体步骤,从而在不改变算法整体流程的前提下,灵活定制部分细节。

其核心思想是 “封装不变,扩展可变”:将通用流程(不变部分)放在父类的模板方法中,将具体实现(可变部分)交给子类,既保证了算法的一致性,又允许子类灵活扩展。

模板方法模式的核心角色

  1. 抽象类(Abstract Class):定义算法的骨架(模板方法),包含多个抽象方法(步骤),由子类实现。模板方法通常用 final 修饰(防止子类重写,JavaScript 中可通过命名规范约束)。
  2. 具体子类(Concrete Class):继承抽象类,实现抽象方法(步骤),完成算法中特定于子类的逻辑。

JavaScript 实现模板方法模式示例:泡茶与泡咖啡

以下以 “冲泡饮品” 为例,实现模板方法模式。场景是:泡茶和泡咖啡的流程相似(都需要 “烧开水→泡原料→倒入杯子→加调料”),但具体步骤的细节不同(如原料不同、调料不同)。通过模板方法模式,将通用流程放在父类,具体步骤由子类实现。

代码实现 - javascript
// -------------- 1. 抽象类:饮品冲泡模板 --------------
class Beverage {
  // 模板方法:定义算法骨架(固定流程)
  make() {
    this.boilWater(); // 步骤1:烧开水(通用步骤)
    this.brew(); // 步骤2:泡原料(子类实现)
    this.pourInCup(); // 步骤3:倒入杯子(通用步骤)
    this.addCondiments(); // 步骤4:加调料(子类实现)
  }

  // 通用步骤:烧开水(所有饮品都需要)
  boilWater() {
    console.log("烧开水(100℃)");
  }

  // 通用步骤:倒入杯子(所有饮品都需要)
  pourInCup() {
    console.log("将饮品倒入杯子");
  }

  // 抽象步骤:泡原料(由子类实现)
  brew() {
    throw new Error("子类必须实现 brew 方法");
  }

  // 抽象步骤:加调料(由子类实现)
  addCondiments() {
    throw new Error("子类必须实现 addCondiments 方法");
  }
}

// -------------- 2. 具体子类:泡茶 --------------
class Tea extends Beverage {
  // 实现泡原料步骤(茶)
  brew() {
    console.log("用热水浸泡茶叶");
  }

  // 实现加调料步骤(茶加柠檬)
  addCondiments() {
    console.log("加入柠檬");
  }
}

// -------------- 3. 具体子类:泡咖啡 --------------
class Coffee extends Beverage {
  // 实现泡原料步骤(咖啡)
  brew() {
    console.log("用热水冲泡咖啡粉");
  }

  // 实现加调料步骤(咖啡加牛奶和糖)
  addCondiments() {
    console.log("加入牛奶和糖");
  }
}

// -------------- 4. 客户端使用 --------------
console.log("=== 泡茶流程 ===");
const tea = new Tea();
tea.make(); // 调用模板方法,执行完整流程
// 输出:
// 烧开水(100℃)
// 用热水浸泡茶叶
// 将饮品倒入杯子
// 加入柠檬

console.log("\n=== 泡咖啡流程 ===");
const coffee = new Coffee();
coffee.make(); // 调用相同的模板方法,执行不同细节
// 输出:
// 烧开水(100℃)
// 用热水冲泡咖啡粉
// 将饮品倒入杯子
// 加入牛奶和糖

代码说明

  1. 抽象类(Beverage)

    • 模板方法 make() 定义了冲泡饮品的固定流程:boilWater() → brew() → pourInCup() → addCondiments(),确保所有饮品遵循相同的步骤顺序。
    • 通用步骤 boilWater() 和 pourInCup() 在父类中实现(所有饮品共享)。
    • 抽象步骤 brew() 和 addCondiments() 由子类实现(不同饮品的差异点)。
  2. 具体子类(Tea、Coffee)

    • 继承 Beverage 并实现抽象步骤:Tea 重写 brew()(泡茶叶)和 addCondiments()(加柠檬);Coffee 重写为 “冲泡咖啡粉” 和 “加牛奶糖”。
    • 无需修改模板方法 make(),即可通过实现不同步骤完成定制化流程。
  3. 核心优势

    • 流程统一:模板方法固定了算法结构,避免子类随意修改流程导致的不一致(如必须先烧开水再泡原料)。
    • 代码复用:通用步骤(如烧开水)在父类中实现,子类无需重复编写。
    • 灵活扩展:新增饮品(如奶茶)只需继承 Beverage 并实现抽象步骤,符合 “开闭原则”。

带钩子方法的扩展示例 - javascript

模板方法模式中还可以加入 “钩子方法(Hook)”,允许子类在特定节点控制流程是否执行,进一步增强灵活性。例如,有些咖啡可能不加调料:

class BeverageWithHook extends Beverage {
  // 钩子方法:默认返回 true(表示需要加调料)
  needCondiments() {
    return true;
  }

  // 重写模板方法,加入钩子判断
  make() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    if (this.needCondiments()) { // 根据钩子决定是否执行加调料步骤
      this.addCondiments();
    }
  }
}

// 不加调料的黑咖啡
class BlackCoffee extends BeverageWithHook {
  brew() {
    console.log("用热水冲泡黑咖啡粉");
  }

  addCondiments() {
    console.log("加入糖(但我不需要)"); // 不会执行
  }

  // 重写钩子:返回 false(不需要加调料)
  needCondiments() {
    return false;
  }
}

console.log("\n=== 泡黑咖啡流程 ===");
const blackCoffee = new BlackCoffee();
blackCoffee.make();
// 输出:
// 烧开水(100℃)
// 用热水冲泡黑咖啡粉
// 将饮品倒入杯子
// (跳过加调料步骤)

钩子方法 needCondiments() 允许子类控制流程分支,使模板更加灵活。

模板方法模式的常见应用场景

  • 框架设计:如前端框架的生命周期函数(React 的 componentDidMount、Vue 的 mounted),框架定义生命周期流程(模板方法),开发者重写具体阶段的逻辑。
  • 流程标准化:如报表生成(固定步骤:获取数据→处理数据→渲染报表)、测试用例(固定步骤:初始化→执行测试→清理)。
  • 代码复用:当多个类的流程相似但细节不同时,用模板方法提取公共流程,减少重复代码。

模板方法模式的核心是 “骨架不变,细节可变”,通过父类定义规则、子类填充细节,实现了算法的一致性和灵活性的平衡,是框架设计中常用的模式。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐