模块方法模式
看看怎么“泡咖啡”和“泡茶”。
//泡咖啡
class Coffee{
prepareRecipe(){
this.boilWater();
this.brewCoffeeGrinds();
this.pourInCup();
this.addSugarAndMilk();
}
boilWater(){
console.log("boil water");
}
brewCoffeeGrinds(){
console.log("brew coffee grinds");
}
pourInCup(){
console.log("pour in cup");
}
addSugerAndMilk(){
console.log("add suger and milk");
}
}
//泡茶
class Tea{
prepareTea(){
this.boilWater();
this.steepTeaBag();
this.pourInCup();
this.addLemon();
}
boilWater(){
console.log("boil water");
}
steepTeaBag(){
console.log("steep tea bag");
}
pourInCup(){
console.log("pour in cup")
}
addLemon(){
console.log("add lemon");
}
}
“泡咖啡”和“泡茶”,步骤是一样的:煮、泡、倒、加。区别在于“泡什么”,“加什么”。
为此,抽象出一个超类:CoffeineBeverage
。
abstract class CoffeineBeverage{
prepareRecipe(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
boilWater(){
console.log("boil water");
}
pourInCup(){
console.log("pour in cup");
}
abstract brew();
abstract addCondiments();
}
Coffee
和Tea
均属咖啡因饮料CoffeineBeverage
,它们的泡制过程也是一样的:prepareRecipe()
,都将经历
boilWater()
、brew()
、pourInCup()
和addCondiments()
四个步骤。至于brew()
时的原料是什么,addCondiments()
时加的调料是什么,这些细节的控制权交给子类,子类自己说了算。
//泡咖啡
class Coffee extends CoffeineBeverage{
brew(){
console.log("brew coffee grinds");
}
addCondiments(){
console.log("add suger and milk");
}
}
//泡茶
class Tea extends CoffeineBeverage{
brew(){
console.log("steep tea bag");
}
addCondiments(){
console.log("add lemon");
}
}
//测试代码
var coffee = new Coffee();
coffee.prepareRecipe();
console.log("---------分割线---------");
var tea = new Tea();
tea.prepareRecipe();
测试结果:
抽象类CoffeineBeverage
就是模块方法
模式,它控制了做事情的总体步骤,但每个步骤的实现细节它不负责,而是由继承它的子类去控制、去实现,因此模块方法
常用于框架中。
但模块方法
并不一定就长得和CoffeineBeverage
一样,像Array.prototype.sort()
就是模块方法
模式,它能够对数组元素进行排序,但怎么个排法,升序?降序?还是按照其他规则,你说了算。
var arr = [1,2,3,4];
//升序排列
var ascendingOrder = function(a,b){
if(a>b) return 1;
else if(a<b) return -1;
else return 0;
}
//降序排列
var descendingOrder = function(a,b){
if(a<b) return 1;
else if(a>b) return -1;
else return 0;
}
arr.sort(ascendingOrder);
console.log(arr);//输出 [ 1, 2, 3, 4 ]
arr.sort(descendingOrder);
console.log(arr);//输出 [ 4, 3, 2, 1 ]
Hook(钩子)
abstract class CoffeineBeverage{
prepareRecipe(){
this.boilWater();
this.brew();
this.pourInCup();
if(this.wantsCondiments()){
this.addCondiments();
}
}
boilWater(){
console.log("boil water");
}
pourInCup(){
console.log("pour in cup");
}
abstract brew();
abstract addCondiments();
wantsCondiments():boolean{
return true;
};
}
class Coffee extends CoffeineBeverage{
brew(){
console.log("brew coffee grinds");
}
addCondiments(){
console.log("add suger and milk");
}
}
class Tea extends CoffeineBeverage{
brew(){
console.log("steep tea bag");
}
wantsCondiments(){
return false;
}
addCondiments(){
console.log("add lemon");
}
}
抽象类CoffeineBeverage
中有个方法wantsCondiments()
,默认返回true
。Coffee
直接继承了该方法,所以泡咖啡时默认加糖和牛奶。
但Tea
中覆写了wantsCondiments()
,返回false
,因此,泡茶时不用加柠檬。
wantsCondiments
这个方法就是一个钩子
,它让addCondiments()
成了一个可选项
,子类也因此有了选择
的权利。
这让我想到了webpack
的钩子
。
如果把webpack的编译打包过程比作是一个输入输出系统
,这个系统就是一个黑盒
,我们只关注输入
和输出
。其中,输入
就是我们配置的文件webpack.config.js
,输出就是编译打包得到的文件
。
默认情况下,webpack
会根据一定的策略将webpack.config.js
配置的参数和webpack本身的默认参数进行合并,得到一个汇总参数,然后依照这个汇总参数设置的规则一路编译打包,输出结果。而webpack提供的形如this.hooks.XXX
的钩子
则让我们有机会改变其默认行程
,从而跳过某些默认步骤或者预设某些动作以便对后面可能发生的事情做出反应。
钩子
在现在的各种前端框架中很常见,找个机会再深入了解。
补充:举例和主要思想来自<HeadFirst设计模式>