# 模板方法模式
模板方法模式是基于继承的一种设计模式,父类封装了子类的算法框架和方法的执行顺序,子类通过继承父类之后,由父类通知子类执行这些方法。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,其中包括一些公共方法和这些方法的执行顺序。子类通过继承抽象父类,不仅继承了整个算法结构,还可以重写父类的方法。
在模板方法模式中,通过增加新的子类就可以给系统增加新的功能,并不需要改动抽象父类以及其他子类,符合开放-封闭原则。
# 传统的模板方法模式
在类似 Java 的传统面向对象语言中,实现模板方法模式往往需要实现一个抽象类,它由一个模板方法和若干个基本方法构成,然后由具体实现类继承和扩展。
沿用书中泡柠檬茶和泡咖啡的例子,可以知道它们都有以下操作步骤:
- 把水煮沸
- 用沸水冲泡原料(泡柠檬茶的原料是茶叶,泡咖啡的原料是咖啡)
- 把冲泡好的饮料倒入杯中
- 往杯中加入调料(泡柠檬茶加入的是柠檬,泡咖啡加入的是牛奶和糖)
用 JavaScript 模拟传统语言实现的代码如下:
// 抽象类-饮料
class Beverage {
// 模板方法
init() {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
// 具体方法-把水煮沸
boilWater() {
console.log('把水煮沸')
}
// 具体方法-把冲泡好的饮料导入杯中
pourInCup() {
console.log('把冲泡好的饮料导入杯中')
}
// 抽象方法-用沸水冲泡原料
brew() {
throw new Error('请在子类中实现对应的抽象方法')
}
// 抽象方法-往杯中加入调料
addCondiments() {
throw new Error('请在子类中实现对应的抽象方法')
}
}
class Coffee extends Beverage {
// 重写抽象方法
brew() {
console.log('用沸水冲泡咖啡')
}
// 重写抽象方法
addCondiments() {
console.log('往杯中加入牛奶和糖')
}
}
const coffee = new Coffee()
coffee.init()
// 把水煮沸
// 用沸水冲泡咖啡
// 把冲泡好的饮料导入杯中
// 往杯中加入牛奶和糖
class LemonTea extends Beverage {
// 重写抽象方法
brew() {
console.log('用沸水冲泡茶叶')
}
// 重写抽象方法
addCondiments() {
console.log('往杯中加入柠檬')
}
}
const lemonTea = new LemonTea()
lemonTea.init()
// 把水煮沸
// 用沸水冲泡茶叶
// 把冲泡好的饮料导入杯中
// 往杯中加入柠檬
# 钩子方法
在上述例子中,泡柠檬茶和泡咖啡的操作步骤被父类限制,如果我们面对的客人不想加调料,那就尴尬了。
钩子方法可以用来解决这个问题,放置钩子是隔离变化的一种常见手段。我们在往杯中加入调料之前,放置一个钩子函数,然后根据钩子函数的意图执行后续操作,这时子类就可以通过实现自己的钩子函数来达到不加调料的目的。
这里我们假定钩子函数的名称为 customWantsCondiments
,那么得到一杯不加糖和牛奶的咖啡的实现代码如下:
// 抽象类-饮料
class Beverage {
// 模板方法
init() {
this.boilWater()
this.brew()
this.pourInCup()
// 判断钩子函数的意图
if (this.customWantsCondiments()) {
this.addCondiments()
}
}
// 具体方法-把水煮沸
boilWater() {
console.log('把水煮沸')
}
// 具体方法-把冲泡好的饮料导入杯中
pourInCup() {
console.log('把冲泡好的饮料导入杯中')
}
// 抽象方法-用沸水冲泡原料
brew() {
throw new Error('请在子类中实现对应的抽象方法')
}
// 抽象方法-往杯中加入调料
addCondiments() {
throw new Error('请在子类中实现对应的抽象方法')
}
// 钩子函数
customWantsCondiments() {
return true // 默认要添加牛奶和糖
}
}
class Coffee extends Beverage {
// 重写抽象方法
brew() {
console.log('用沸水冲泡咖啡')
}
// 重写钩子函数
customWantsCondiments() {
return false
}
}
const coffee = new Coffee()
coffee.init()
// 把水煮沸
// 用沸水冲泡咖啡
// 把冲泡好的饮料导入杯中
# JavaScript 中的模板方法模式
模板方法模式是为数不多的基于继承的设计模式,但 JavaScript 语言没有提供真正的类式继承,继承是通过对象与对象之间的委托来实现的。因此,在 JavaScript 中,我们往往通过高阶函数来实现模板方法模式。
通过高阶函数形式改写后,得到一杯不加牛奶和糖的咖啡的代码如下:
const Beverage = (
{ brew, addCondiments, customWantsCondiments } = {
brew() {
throw new Error('必须传递 brew 方法')
},
addCondiments() {
throw new Error('必须传递 addCondiments 方法')
},
customWantsCondiments() {
return true
}
}
) => {
const boilWater = () => console.log('把水煮沸')
const pourInCup = () => console.log('把冲泡好的饮料导入杯中')
const F = function () {}
F.prototype.init = () => {
boilWater()
brew()
pourInCup()
if (customWantsCondiments()) {
addCondiments()
}
}
return F
}
const Coffee = Beverage({
brew() {
console.log('用沸水冲泡咖啡')
},
customWantsCondiments() {
return false
}
})
const coffee = new Coffee()
coffee.init()
// 把水煮沸
// 用沸水冲泡咖啡
// 把冲泡好的饮料导入杯中
← 组合模式