8_模板方法模式
1 模板方法模式的定义和组成
1.1 定义: 基于继承的设计模式
1.2 组成
1. 抽象父类
- 封装了子类的算法框架
- 公共方法
- 封装子类中所有方法的执行顺序
2. 具体的实现子类
2 第一个例子 -- Coffee or Tea
2.1 先泡一杯咖啡
class Coffee {
constructor() {
this.init()
}
init() {
this.boilWater()
this.brewCoffeeGriends()
this.pourInCup()
this.addSugarAndMilk()
}
boilWater() {
console.log('把水煮沸')
}
brewCoffeeGriends() {
console.log('用沸水冲泡咖啡')
}
pourInCup() {
console.log('把咖啡倒进杯子')
}
addSugarAndMilk() {
console.log('加糖和牛奶')
}
}
let coffee = new Coffee()
2.2 再泡一杯茶
class Tea {
constructor() {
this.init()
}
init() {
this.boilWater()
this.steepTeaBag()
this.pourInCup()
this.addLemon()
}
boilWater() {
console.log('把水煮沸')
}
steepTeaBag() {
console.log('用沸水浸泡茶叶')
}
pourInCup() {
console.log('把茶水倒进杯子')
}
addLemon() {
console.log('加柠檬')
}
}
let tea = new Tea()
2.3 分离共同点
泡咖啡 | 泡茶 |
---|---|
把水煮沸 | 把水煮沸 |
用沸水冲泡咖啡 | 用沸水浸泡茶叶 |
把咖啡倒进杯子 | 把茶水倒进杯子 |
加糖和牛奶 | 加柠檬 |
不同点
- 原料不同 -- 咖啡&茶 - 抽象为“饮料”
- 泡的方式不同 -- 冲泡&浸泡 - 抽象为“泡”
- 加入的调料不同 -- 糖和牛奶&柠檬 - 抽象为“调料”
步骤
- 把水煮沸
- 用沸水泡饮料
- 把饮料倒进杯子
- 加调料
2.4 实现抽象类
class Beverage {
constructor() {
this.init()
}
// 模板方法--封装了子类的算法框架
init() {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
boilWater() {
console.log('把水煮沸')
}
// 空方法,应该由子类重写
brew() {}
pourInCup() {}
addCondiments() {}
}
2.5 实现Coffee子类和Tea子类
1. Coffee子类
class Coffee extends Beverage {
constructor() {
super()
}
brew() {
console.log('用沸水冲泡咖啡')
}
pourInCup() {
console.log('把咖啡倒进杯子')
}
addCondiments() {
console.log('加糖和牛奶')
}
}
let coffee = new Coffee()
2. Tea子类
class Tea extends Beverage {
constructor() {
super()
}
brew() {
console.log('用沸水浸泡茶叶')
}
pourInCup() {
console.log('把茶水倒进杯子')
}
addCondiments() {
console.log('加柠檬')
}
}
let tea = new Tea()
3 抽象类
3.1 作用
- 抽象类不允许实例化
if (new.target === Beverage) {
throw new Error('Beverage class 不允许实例化')
}
- 非抽象继承子类必须重写抽象类的所有抽象方法
3.2 抽象方法和具体方法
- 抽象方法:被声明在抽象类中,无具体的实现过程
- 具体方法:子类中的具体实现方法可以选择放在抽象类中,以达复用效果
3.3 解决js没有抽象类的缺点
缺点
- 无类型检测
- 无法保证子类会重写父类中的抽象方法
解决方案
- 用鸭子类型模拟接口检查,确保子类中确实重写了父类的方法
- 直接让父类的抽象方法抛出异常
class Beverage {
constructor() {
// 抽象类不允许实例化
if (new.target === Beverage) {
throw new Error('Beverage class 不允许实例化');
}
this.init()
}
// 模板方法--封装了子类的算法框架
init() {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
boilWater() {
console.log('把水煮沸')
}
// 空方法,应该由子类重写
brew() {
throw new Error('子类必须重写 brew 方法')
}
pourInCup() {
throw new Error('子类必须重写 pourInCup 方法')
}
addCondiments() {
throw new Error('子类必须重写 addCondiments 方法')
}
}
4 模板方法模式的使用场景
- 常用于架构师搭建项目框架
5 钩子方法
- 放置钩子是隔离变化的常见手段
- 钩子方法的返回结果决定了模板方法后面部分的执行步骤
钩子 customerCondiments
抽象类 -- 父类
class Beverage {
constructor() {
// 001 抽象类不允许实例化
if (new.target === Beverage) {
throw new Error('Beverage class 不允许实例化');
}
this.init()
}
// 模板方法--封装了子类的算法框架
init() {
this.boilWater()
this.brew()
this.pourInCup()
if(this.customerCondiments()) {
this.addCondiments()
}
}
boilWater() {
console.log('把水煮沸')
}
brew() {
throw new Error('子类必须重写 brew 方法')
}
pourInCup() {
throw new Error('子类必须重写 pourInCup 方法')
}
addCondiments() {
throw new Error('子类必须重写 addCondiments 方法')
}
customerCondiments() {
return true // 默认需要调料
}
}
子类
class CoffeeWithHook extends Beverage {
constructor() {
super()
}
brew() {
console.log('用沸水冲泡咖啡')
}
pourInCup() {
console.log('把咖啡倒进杯子')
}
addCondiments() {
console.log('加糖和牛奶')
}
customerCondiments() {
return window.confirm('请问需要调料吗?')
}
}
let coffeeWithHook = new CoffeeWithHook()
6 好莱坞原则
- 好莱坞原则允许底层组件将自己挂钩到高层组件中,而高层组件会决定 when&how 使用这些底层组件
“别调用我们,我们会调用你”
应用场景
- 模板方法模式
- 发布订阅模式
- 回调函数