模板方法模式是一种只需要继承就可以实现的非常简单的模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法导致一种反向的控制结构,这种结构就是传说中的“好莱坞法则”,即“别找找我们,我们找你”,这指的是父类调用一个类的操作,而不是相反。假如存在一些平行的子类,各个子类之间存在相同行为,并且这些相同的行为会在各个子类重复出现,采用模板方法模式就可以把相同的行为搬移到另外一个单一的地方。因此,模板方法模式具体体现是面向对象编程编程语言里的抽象类(以及其中的抽象方法),以及继承该抽象类(和抽象方法)的子类。
采用来自《Head First 设计模式》中咖啡与茶的例子来引入模板方法模式:
得到一杯咖啡的步骤:
<span style="font-size:14px;">var Coffee = function(){};
Coffee.prototype.boilWater = function(){
console.log( '把水煮沸' );
};
Coffee.prototype.brewCoffeeGriends = function(){
console.log( '用沸水冲泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
console.log( '把咖啡倒进杯子' );
};
Coffee.prototype.addSugarAndMilk = function(){
console.log( '加糖和牛奶' );
};
Coffee.prototype.init = function(){
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugarAndMilk();
};
var coffee = new Coffee();
coffee.init();</span>
得到一本茶的步骤:
var Tea = function(){};
Tea.prototype.boilWater = function(){
console.log( '把水煮沸' );
};
Tea.prototype.steepTeaBag = function(){
console.log( '用沸水浸泡茶叶' );
};
Tea.prototype.pourInCup = function(){
console.log( '把茶水倒进杯子' );
};
Tea.prototype.addLemon = function(){
console.log( '加柠檬' );
};
Tea.prototype.init = function(){
this.boilWater();
this.steepTeaBag();
this.pourInCup();
this.addLemon();
};
var tea = new Tea();
tea.init();
很明显两种方法可以整理成如下步骤:煮沸水<<冲饮料<<装杯<<加调料
于是现在可以创建一个抽象父类来表示泡一杯饮料的整个过程。用Beverge表示所需饮料,代码如下:
var Beverage = function(){};
Beverage.prototype.boilWater = function(){
console.log( '把水煮沸' );
};
Beverage.prototype.brew = function(){}; // 空方法,应该由子类重写
Beverage.prototype.pourInCup = function(){}; // 空方法,应该由子类重写
Beverage.prototype.addCondiments = function(){}; // 空方法,应该由子类重写
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};
模板方法模式是一种严重依赖抽象类的设计模式,而在javascript语言层面并没有提供抽象类的支持,很难模拟抽象类的实现,借助Java中抽象类的作用,对javascript没有抽象类时做出的让步和变通。我们采用一种解决方法是让Beverage.prototype.brew等方法抛出一个异常,如果粗心忘记编写 Beverage.prototype.brew方法,那么在程序运行时会抛出一个错误。
var Beverage = function( param ){
var boilWater = function(){
console.log( '把水煮沸' );
};
var brew = param.brew || function(){
throw new Error( '必须传递brew 方法' );
};
var pourInCup = param.pourInCup || function(){
throw new Error( '必须传递pourInCup 方法' );
};
var addCondiments = param.addCondiments || function(){
throw new Error( '必须传递addCondiments 方法' );
};
var F = function(){};
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};
var Coffee = Beverage({
brew: function(){
console.log( '用沸水冲泡咖啡' );
},
pourInCup: function(){
console.log( '把咖啡倒进杯子' );
},
addCondiments: function(){
console.log( '加糖和牛奶' );
}
});
var Tea = Beverage({
brew: function(){
console.log( '用沸水浸泡茶叶' );
},
pourInCup: function(){
console.log( '把茶倒进杯子' );
},
addCondiments: function(){
console.log( '加柠檬' );
}
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();
模板方法模式主要采用的著名的“好莱坞原则”的设计原则,即“不要来找我。我会给你打电话”。在这段代码中,我们把brew、pourInCup、addCondiments这些方法依次传入Beverage函数,Beverage函数调用之后返回构造器F。F类中包含了“模板方法”F.prototype.init。跟继承得到的效果一样,该模板方法依然封装了饮料子类的算法框架。