# 模板方法模式
# 参考
- http://c.biancheng.net/view/1376.html (opens new window)
- https://refactoringguru.cn/design-patterns/template-method (opens new window)
# 定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤(基于继承)。
例如,一个人每天会起床、吃饭、上班、睡觉等,每个人“上班”的内容不同,而且有可能在吃饭或者睡觉前需要做不一样的事情,我们把这些通用一致的流程或格式的实例定义成模板,允许使用者根据自己的具体需求去更新它。
# 应用场景
在应用中往往存在这样的代码,一个功能中,因各模块存在部分不同的逻辑,这时候就会出现各种条件语句。随着需求变更,这部分代码会变越来越庞大,充斥着冗长的 if-else
面条代码,让代码维护、阅读难度变大。
这时候可以用模板方法模式,利用语言的多态机制,提取公共部分到为抽象父类,将差异化逻辑、条件语句转移到为一个个具体子类。在保证了代码复用的同时,减少了聚合在一起的条件语句。
# 实现思路
通常包含以下两个主要角色:
抽象父类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
基本方法:是整个算法中的一个步骤,包含以下几种类型:
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
具体子类,实现抽象类中所定义的抽象方法和钩子方法。
# 优点
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
# 缺点
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
# 代码实例
// 泡茶跟冲咖啡有些步骤是一致的,所以将这些一致的行为抽离到抽象父类Beverage中,
// 然后具体差异分别在Coffee、tea中
abstract class Beverage {
init() {
this.brew()
this.pourInCup()
this.wantCondiments() && this.addCondiments()
}
boilWater() {
console.log('把水煮沸')
}
// 钩子函数用于在子类中改变模板流程
wantCondiments() {
return true
}
abstract brew(): void
abstract pourInCup(): void
abstract addCondiments(): void
}
class Coffee extends Beverage {
brew() {
console.log('用沸水冲泡咖啡')
}
pourInCup() {
console.log('将咖啡倒进杯子')
}
addCondiments() {
console.log('加糖和牛奶')
}
}
class PureCoffee extends Coffee {
wantCondiments() {
return false
}
}
class Tea extends Beverage {
brew() {
console.log('用沸水冲泡茶叶')
}
pourInCup() {
console.log('将茶倒进杯子')
}
addCondiments() {
console.log('加柠檬')
}
}
const coffee = new Coffee()
const pureCoffee = new PureCoffee()
const tea = new Tea()
console.log('\n')
coffee.init()
console.log('\n')
pureCoffee.init()
console.log('\n')
tea.init()