模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
假如我们有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为。如果相同和不同的行为都混合在各个子类的实现中,说明这些相同的行为会在各个子类中重复出现。但实际上,相同的行为可以被搬移到另外一个单一的地方,模板方法模式就是为解决这个问题而生的。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。这也很好地体现了泛化的思想。
基于继承实现一个模板方法模式
const Beverage=function(){}
Beverage.prototype.boilWater=function(){
console.log('把水煮沸')
}
Beverage.prototype.brew=function(){
throw new Error ('子类必须重写brew方法')
}
Beverage.prototype.pourInCup=function(){
throw new Error ('子类必须重写pourInCup方法')
}
Beverage.prototype.addCondiments=function(){
throw new Error('子类必须重写addCondiments方法')
}
Beverage.prototype.customerWantsCondiments=function(){
return true
}
Beverage.prototype.init=function(){
this.boilWater()
this.brew()
this.pourInCup()
if(this.customerWantsCondiments()){
this.addCondiments()
}
}
const CoffeeWithHook=function(){}
CoffeeWithHook.prototype=new Beverage()
CoffeeWithHook.prototype.brew=function(){
console.log('用沸水冲泡咖啡')
}
CoffeeWithHook.prototype.pourInCup=function(){
console.log('把咖啡倒进杯子')
}
CoffeeWithHook.prototype.addCondiments=function(){
console.log('加糖和牛奶')
}
CoffeeWithHook.prototype.customerWantsCondiments=function(){
return window.confirm('请问需要调料吗?')
}
let coffeeWithHook=new CoffeeWithHook()
coffeeWithHook.init()
其中的customerWantsCondiments是钩子方法,子类可以通过自己定义此方法来控制父类的行为,当然这个钩子需要时父类预设好的。
好莱坞原则
好莱坞无疑是演员的天堂,但好莱坞也有很多找不到工作的新人演员,许多新人演员在好莱坞把简历递给演艺公司之后就只有回家等待电话。有时候该演员等得不耐烦了,给演艺公司打电话询问情况,演艺公司往往这样回答:“不要来找我,我会给你打电话。”
在设计中,这样的规则就称为好莱坞原则。在这一原则的指导下,我们允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。
模板方法模式是好莱坞原则的一个典型使用场景,它与好莱坞原则的联系非常明显,当我们用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类,只负责提供一些设计上的细节。
除此之外,好莱坞原则还常常应用于其他模式和场景,例如发布-订阅模式和回调函数。
-
发布—订阅模式
在发布—订阅模式中,发布者会把消息推送给订阅者,这取代了原先不断去fetch消息的形式。例如假设我们乘坐出租车去一个不了解的地方,除了每过5秒钟就问司机“是否到达目的地”之外,还可以在车上美美地睡上一觉,然后跟司机说好,等目的地到了就叫醒你。这也相当于好莱坞原则中提到的“别调用我们,我们会调用你”。 -
回调函数
在ajax异步请求中,由于不知道请求返回的具体时间,而通过轮询去判断是否返回数据,这显然是不理智的行为。所以我们通常会把接下来的操作放在回调函数中,传入发起ajax异步请求的函数。当数据返回之后,这个回调函数才被执行,这也是好莱坞原则的一种体现。把需要执行的操作封装在回调函数里,然后把主动权交给另外一个函数。至于回调函数什么时候被执行,则是另外一个函数控制的。
javascript高阶函数实现模板方法模式
const Beverage=function(){
const boilWater=function(){
console.log('把水煮沸')
}
const brew=params.brew||function(){
throw new Error('必须传递brew方法')
}
const pourInCup=parasm.pourInCup||function(){
throw new Error('必须传递pourInCup方法')
}
const addCondiments=params.addCondiments||function(){
throw new Error('必须传递addCondiments方法')
}
const F=function(){}
F.prototype.init=function(){
boilWater()
brew()
pourInCup()
addCondiments()
}
return F
}
const Coffee=Beverage({
brew:function(){
console.log('用沸水冲泡咖啡')
},
pourInCup:function(){
console.log('把咖啡倒进杯子')
},
addCondiments:function(){
console.log('加糖和牛奶')
}
})
const Tea=Beverage({
brew:function(){
console.log('用沸水冲泡茶叶')
},
pourInCup:function(){
console.log('把茶倒进杯子')
},
addCondiments:function(){
console.log('加柠檬')
}
})
const coffee=new Coffee()
coffee.init()
const tea=new Tea()
tea.init()
模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放-封闭原则的。
但在JavaScript中,我们很多时候都不需要依样画瓢地去实现一个模版方法模式,高阶函数是更好的选择。
网友评论