模板方法模式是一种只须要使用继承就能够实现的设计模式,它一般由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。在抽象父类中封装了子类的算法框架,包括一些公共的方法以及子类中全部方法的执行顺序。子类经过继承父类,也继承了整个算法结构,以及重写父类中一些具体的方法。javascript
下面经过咖啡与茶的经典例子来说解模板方法模式的具体实现。
先泡一杯咖啡
泡一杯咖啡一般包括如下步骤:java
经过代码实现上述步骤为:程序员
class Coffee {
boilWater () {
console.log('把水煮沸')
}
brewCoffeeGriends () {
console.log('用沸水冲泡咖啡')
}
pourInCup () {
console.log('把咖啡倒进杯子里')
}
addSugarAndMilk () {
console.log('加糖和牛奶')
}
init () {
this.boilWater()
this.brewCoffeeGriends()
this.pourInCup()
this.addSugarAndMilk()
}
}
const coffee = new Coffee()
coffee.init()
复制代码
泡一壶茶
而泡一壶茶的步骤跟泡咖啡的步骤相似:web
用代码描述以下:ajax
class Tea {
boilWater () {
console.log('把水煮沸')
}
steepTeaBag () {
console.log('用沸水浸泡茶叶')
}
pourInCup () {
console.log('把茶水倒进杯子里')
}
addLemon () {
console.log('加柠檬')
}
init () {
this.boilWater()
this.steepTeaBag()
this.pourInCup()
this.addLemon()
}
}
const tea = new Tea()
tea.init()
复制代码
分离出共同点
对比泡咖啡和泡茶步骤:算法
泡咖啡 | 泡茶 |
---|---|
把水煮沸 | 把水煮沸 |
用沸水煮咖啡 | 用沸水浸泡茶叶 |
把咖啡倒进杯子里 | 把茶水倒进杯子里 |
加糖和牛奶 | 加柠檬 |
分析表格咱们得出泡咖啡和泡茶的不一样点:设计模式
因而不论是泡咖啡仍是泡茶,咱们能够用以下代码描述:架构
class Beverage {
boilWater () {
console.log('把水煮沸')
}
// 空方法,由子类重写
brew () {}
// 空方法,由子类重写
pourInCup () {}
// 空方法,由子类重写
addCondiments () {}
init () {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
}
复制代码
建立泡咖啡子类:框架
class Coffee extends Beverage {
brew () {
console.log('用沸水煮咖啡')
}
pourInCup () {
console.log('把咖啡倒进杯子里')
}
addCondiments () {
console.log('加糖和牛奶')
}
}
const coffee = new Coffee()
coffee.init()
复制代码
这样咱们经过继承Beverage类,重写一些具体方法,就完成了泡咖啡子类的实现。泡茶的Tea实现相似,这里就再也不具体写代码。dom
其实模板方法是一种严重依赖抽象类的设计模式,在Java和其它一些静态类型的语言才有,而Javascript中是没有提供抽象的支持的。在Java中有两种类,一种是抽象类,一种是具体类。具体类是用来实例化的,而抽象类不能被实例化,是用来被继承的。上面的泡咖啡和泡茶的例子中,咱们抽象了饮料类,里面包含了一些空方法,若是是抽象类实现的,那这些方法能够被定义为抽象方法,继承该抽象类的子类必须实现那些抽象方法,不然编译的时候就会报错。既然JavaSript中没有抽象类,那该怎么解决了?下面提供两种变通方案:
架构师搭建项目
模板方法模式常被架构师用来搭建项目的框架,架构师定义好了框架的骨架,程序员继承框架的结构以后,负责往里面填写内容。好比Java中的HttpServelet技术,一个基于HttpServelet的程序包含7个生命周期,七个生命周期对应7个do方法
它还提供了一个service方法,也就是模板方法,这个方法规定了这些do方法的执行顺序,而这些do方法的具体实现则须要HttpServelet的子类来提供。
web构建UI组件
咱们知道构建一个组件的过程通常以下:
分析上述步骤,咱们发现第一步和第四步通常是相同的,中间两步可能会有变化,因而咱们能够把上面四步抽象到一个建立组件的父类中,父类提供第一步和第四步的实现,中间两步由具体的子类重写。
在模板方法模式中,咱们在父类中封装了子类的算法框架,这些算法框架适用大多数状况,可是也会有一些特殊状况。好比考虑泡咖啡中,可能有些顾客喝咖啡是须要加调料的,可是也有部分顾客不须要加调料,那怎么去定制这个需求,不让子类彻底受父类的模板方法约束,钩子方法就是用来解决这个问题的。钩子是隔离变化的一种经常使用手段,咱们通常在父类中容易变化的地方放置钩子,钩子通常会提供一个默认实现,究竟要不要彻底跟父类的算法框架同样,子类能够自行决定。下面实现一个带customerWantCondiments钩子的饮料类:
class Beverage {
boilWater () {
console.log('把水煮沸')
}
// 空方法,由子类重写
brew () {
throw new Error('子类必须重写brew方法')
}
// 空方法,由子类重写
pourInCup () {
throw new Error('子类必须重写pourInCup方法')
}
// 空方法,由子类重写
addCondiments () {
throw new Error('子类必须重写addCondiments方法')
}
// 默认须要调料
customerWantCondiments () {
return true
}
init () {
this.boilWater()
this.brew()
this.pourInCup()
if (this.customerWantCondiments()) {
this.addCondiments()
}
}
}
复制代码
那么泡咖啡的子类能够这样实现:
class CoffeeWithHook extends Beverage {
brew () {
console.log('用沸水煮咖啡')
}
pourInCup () {
console.log('把咖啡倒进杯子里')
}
addCondiments () {
console.log('加糖和牛奶')
}
customerWantCondiments () {
return window.confirm('请问须要调料吗?')
}
}
const coffeeWithHook = new CoffeeWithHook()
coffeeWithHook.init()
复制代码
在模板方法模式中,咱们还学到了一个新的设计原则----著名的好莱坞原则。
在好莱坞中,许多新人演员也找不到工做,他们通常把简历递给演艺公司后就只有回家等电话,有时候等得不耐烦的演员就会打电话给演艺公司询问状况怎么样,但获得的回答是:“不要来找我,我会打电话给你”。
在设计中,这种规则就称为好莱坞原则。这种原则的思路是,咱们容许底层组件将本身挂钩到高层组件中,而高层组件决定何时,以何种方式去使用这些底层组件,高层组件对待底层组件的方式就跟演艺公司对待新人演员同样,都是“别调用咱们,咱们会调用你”。
模板方法模式就是好莱坞原则的一个典型使用场景,当咱们用模板方法编写一个程序时,就意味着子类放弃了对本身的控制权,而是改成父类去主动调用子类的方法,子类只是提供一些具体的方法的实现。除了模板方法,如下两种场景也体现了好莱坞原则:
模板方法模式是一种典型的经过封装变化提升系统扩展性的设计模式。在一个使用了模板方法的模式中,子类的方法和执行顺序都是不变的,因此咱们把这部分逻辑抽象到父类的模板方法里,可变的逻辑部分经过不一样的子类来实现,经过增长新的子类,给系统添加新的功能,不须要改动抽象父类以及其它子类,符合开放-封闭原则。