模板方法模式
首发于:2022-04-06
基本概念
模板方法模式(Template Method Pattern)父类中定义一组操作算法骨架,而将一些实现步骤延迟到子类中,使得子类可以不改变父类的算法结构的同时,重新定义算法中的某些实现步骤。模板方法模式的关键是算法步骤的骨架和具体实现分离。
现实生活中的例子
这里举个经典的咖啡厅例子,咖啡厅制作饮料的过程有一些类似的步骤:
- 先把水煮沸
- 冲泡饮料(咖啡、茶、牛奶)
- 倒进杯子中
- 最后加一些调味料(咖啡伴侣、枸杞、糖)
无论冲饮的是咖啡、茶、牛奶,他们的制作过程都类似,可以被总结为这几个流程。也就是说这个流程是存在着类似的流程结构的,这就给我们留下了将操作流程抽象封装出来的余地。
再比如,我们可以有一个游戏的模板方法,游戏嘛,肯定都有游戏初始化,开始游戏,结束游戏这样的操作,至于具体的游戏,可以是狼人杀、密室逃脱、甚至是踢足球、躲猫猫都可以。
应用场景
- 如果知道一个算法所需的关键步骤,而且很明确这些步骤的执行顺序,但是具体的实现是未知的、灵活的,那么这时候就可以使用模板方法模式来将算法步骤的框架抽象出来;
- 重要而复杂的算法,可以把核心算法逻辑设计为模板方法,周边相关细节功能由各个子类实现;
- 模板方法模式可以被用来将子类组件将自己的方法挂钩到高层组件中,也就是钩子,子类组件中的方法交出控制权,高层组件在模板方法中决定何时回调子类组件中的方法,类似的用法场景还有发布-订阅模式、回调函数;
优缺点
优点:
- 封装了不变部分,扩展可变部分, 把算法中不变的部分封装到父类中直接实现,而可变的部分由子类继承后再具体实现;
- 提取了公共代码部分,易于维护, 因为公共的方法被提取到了父类,那么如果我们需要修改算法中不变的步骤时,不需要到每一个子类中去修改,只要改一下对应父类即可;
- 行为被父类的模板方法固定, 子类实例只负责执行模板方法,具备可扩展性,符合开闭原则;
缺点:
- 增加了系统复杂度,主要是增加了的抽象类和类间联系,需要做好文档工作;
实现
下面是一种通用实现,实际使用的时候参照这个模式稍作修改即可:
js
/* 抽象父类 */
class AbstractClass {
constructor() {
if (new.target === AbstractClass) {
throw new Error('抽象类不能直接实例化!')
}
}
/* 共用方法 */
operate1() { console.log('operate1') }
/* 抽象方法 */
operate2() { throw new Error('抽象方法不能调用!') }
/* 模板方法 */
templateMethod() {
this.operate1()
this.operate2()
}
}
/* 实例子类,继承抽象父类 */
class ConcreteClass extends AbstractClass {
constructor() { super() }
/* 覆盖抽象方法 operate2 */
operate2() { console.log('operate2') }
}
const instance = new ConcreteClass()
instance.templateMethod()
// 输出:operate1
// 输出:operate2