模板方法模式
用咖啡厅制作饮品的例子理解模板方法模式
问题
使用模板方法模式实现咖啡厅制作饮品的场景。咖啡和茶的制作流程相似,但具体步骤不同。
解答
模板方法模式概念
模板方法模式在父类中定义算法骨架,将部分步骤延迟到子类实现。父类控制流程,子类负责具体实现。
实现饮品制作
// 抽象类:饮品
class Beverage {
// 模板方法,定义制作流程(不可被子类覆盖)
make() {
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
// 通用步骤:烧水
boilWater() {
console.log('烧开水');
}
// 通用步骤:倒入杯中
pourInCup() {
console.log('倒入杯中');
}
// 抽象方法:冲泡(子类必须实现)
brew() {
throw new Error('子类必须实现 brew 方法');
}
// 抽象方法:添加调料(子类必须实现)
addCondiments() {
throw new Error('子类必须实现 addCondiments 方法');
}
}
// 具体类:咖啡
class Coffee extends Beverage {
brew() {
console.log('用沸水冲泡咖啡粉');
}
addCondiments() {
console.log('加糖和牛奶');
}
}
// 具体类:茶
class Tea extends Beverage {
brew() {
console.log('用沸水浸泡茶叶');
}
addCondiments() {
console.log('加柠檬');
}
}
// 使用
const coffee = new Coffee();
console.log('--- 制作咖啡 ---');
coffee.make();
const tea = new Tea();
console.log('--- 制作茶 ---');
tea.make();
输出:
--- 制作咖啡 ---
烧开水
用沸水冲泡咖啡粉
倒入杯中
加糖和牛奶
--- 制作茶 ---
烧开水
用沸水浸泡茶叶
倒入杯中
加柠檬
添加钩子方法
钩子方法让子类可以选择性地改变模板流程:
class Beverage {
make() {
this.boilWater();
this.brew();
this.pourInCup();
// 钩子:是否需要添加调料
if (this.wantCondiments()) {
this.addCondiments();
}
}
boilWater() {
console.log('烧开水');
}
pourInCup() {
console.log('倒入杯中');
}
brew() {
throw new Error('子类必须实现 brew 方法');
}
addCondiments() {
throw new Error('子类必须实现 addCondiments 方法');
}
// 钩子方法:默认需要调料,子类可覆盖
wantCondiments() {
return true;
}
}
// 黑咖啡:不加调料
class BlackCoffee extends Beverage {
brew() {
console.log('用沸水冲泡咖啡粉');
}
addCondiments() {
console.log('加糖和牛奶');
}
// 覆盖钩子:不要调料
wantCondiments() {
return false;
}
}
const blackCoffee = new BlackCoffee();
console.log('--- 制作黑咖啡 ---');
blackCoffee.make();
输出:
--- 制作黑咖啡 ---
烧开水
用沸水冲泡咖啡粉
倒入杯中
函数式实现
不用类,用高阶函数实现:
// 模板函数
function makeBeverage(options) {
const { brew, addCondiments, wantCondiments = () => true } = options;
return function () {
console.log('烧开水');
brew();
console.log('倒入杯中');
if (wantCondiments()) {
addCondiments();
}
};
}
// 创建具体饮品
const makeCoffee = makeBeverage({
brew: () => console.log('用沸水冲泡咖啡粉'),
addCondiments: () => console.log('加糖和牛奶'),
});
const makeTea = makeBeverage({
brew: () => console.log('用沸水浸泡茶叶'),
addCondiments: () => console.log('加柠檬'),
});
console.log('--- 制作咖啡 ---');
makeCoffee();
console.log('--- 制作茶 ---');
makeTea();
关键点
- 模板方法:父类定义算法骨架,固定执行顺序
- 抽象方法:子类必须实现的步骤,体现差异化
- 钩子方法:子类可选覆盖,用于控制流程分支
- 好莱坞原则:「别调用我们,我们会调用你」——父类控制流程,子类被动调用
- 适用场景:多个类有相同算法结构,但部分步骤实现不同
目录