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 分离共同点

泡咖啡 泡茶
把水煮沸 把水煮沸
用沸水冲泡咖啡 用沸水浸泡茶叶
把咖啡倒进杯子 把茶水倒进杯子
加糖和牛奶 加柠檬
不同点
  • 原料不同 -- 咖啡&茶 - 抽象为“饮料”
  • 泡的方式不同 -- 冲泡&浸泡 - 抽象为“泡”
  • 加入的调料不同 -- 糖和牛奶&柠檬 - 抽象为“调料”
步骤
  1. 把水煮沸
  2. 用沸水泡饮料
  3. 把饮料倒进杯子
  4. 加调料

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 作用

  1. 抽象类不允许实例化
if (new.target === Beverage) {
  throw new Error('Beverage class 不允许实例化')
}
  1. 非抽象继承子类必须重写抽象类的所有抽象方法

3.2 抽象方法和具体方法

  • 抽象方法:被声明在抽象类中,无具体的实现过程
  • 具体方法:子类中的具体实现方法可以选择放在抽象类中,以达复用效果

3.3 解决js没有抽象类的缺点

缺点
  1. 无类型检测
  2. 无法保证子类会重写父类中的抽象方法
解决方案
  1. 用鸭子类型模拟接口检查,确保子类中确实重写了父类的方法
  2. 直接让父类的抽象方法抛出异常
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 使用这些底层组件

“别调用我们,我们会调用你”

应用场景

  1. 模板方法模式
  2. 发布订阅模式
  3. 回调函数
posted on 2023-05-09 10:50  pleaseAnswer  阅读(86)  评论(0编辑  收藏  举报