**定义:**模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常 在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺 序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。说白了就是通过javascript的原型实现变相的继承设计出来的一种简单的设计模式。
举个例子:咖啡与茶是一个经典的例子,首先,我们先来泡一杯咖啡,如果没有什么太个性化的需求,泡咖啡的步骤通常如下: (1) 把水煮沸 (2) 用沸水冲泡咖啡 (3) 把咖啡倒进咖啡杯(4) 加糖和牛奶 ;接下来,开始准备我们的茶,泡茶的步骤跟泡咖啡的步骤相差并不大: (1) 把水煮沸 (2) 用沸水浸泡茶叶 (3) 把茶水倒进茶杯 (4) 加柠檬
通过对比咖啡与茶的制作步骤可以得出一个公用步骤,这就是模板方法,其实泡茶与泡咖啡的步骤是大同小异的:
把水煮沸 => 用沸水冲泡饮料(这里将茶/咖啡抽象为“饮料”)=> 将饮料倒入杯中 => 加入调料
用代码实现下这个简单的例子
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();
};
// 冲咖啡
var Coffee = function(){};
Coffee.prototype = new Beverage(); // 通过继承Beverage,创建一个咖啡构造函数
// boilWater 方法已经在父类中实现,故不需要重写
Coffee.prototype.brew = function(){ // 重写冲泡饮料方法
console.log( '用沸水冲泡咖啡' );
};
Coffee.prototype.pourInCup = function(){ // 重写倒饮料方法
console.log('把咖啡倒进咖啡杯')
}
Coffee.prototype.addCondiments = function(){ // 重写加入调料方法
console.log('加入糖和牛奶')
}
// 实例化制作一杯咖啡
var Coffee = new Coffee();
Coffee.init();
// 同理可以制作一杯茶
var Tea = function(){};
Tea.prototype = new Beverage();
Tea.prototype.brew = function(){
console.log( '用沸水浸泡茶叶' );
};
Tea.prototype.pourInCup = function(){
console.log( '把茶倒进茶杯' );
};
Tea.prototype.addCondiments = function(){
console.log( '加柠檬' );
};
var tea = new Tea();
tea.init();
通过这个例子可以看出来该模式中的模板方法就是 Beverage.prototype.init这个方法,模板方法模式将一个模板的算法过程中的每个步骤事先定义好,包装成一个算法模板,我们不需要关注这模块之间的执行顺序和逻辑,只需要重写模板中每个子模块的方法,这能让我们清晰的看到代码执行步骤。
**应用场景:**流程固定,但流程细节不同时可使用,例如对于前端来说,一个表单组件中有一个新建的操作和一个更新的操作,创建和更新都需要经过以下流程
(1)验证表单必填信息
(2)格式化请求参数
(3)发送请求
(4)刷新列表
其中(1)(4)是一样的操作,但是(2)是因为不同的操作需要传递不一样的参数,(3)是发送请求是会有不一样的请求地址,所以需要重写
var Order = function(){} // 定义订单“抽象类”
Order.prototype.checkForm = function(){ // 验证表单的步骤是相同的所有可以直接在父类中实现
console.log( '验证表单' );
}
Order.prototype.formatParams = function(){}; // 格式化入参空方法,应该由子类重写
Order.prototype.setUrl = function(){}; // 设置请求路径空方法,应该由子类重写
Order.prototype.refreshList = function(){ // 刷新列表的步骤是相同的所有可以直接在父类中实现
console.log('刷新列表')
};
Order.prototype.submitOrder = function(){ // 提交表单方法
this.checkForm();
this.formatParams();
this.setUrl();
this.refreshList();
};
// 新增订单
var CreateOrder = function(){};
CreateOrder.prototype = new Order(); // 通过继承Order,创建一个新增订单构造函数
// checkForm、refreshList方法已经在父类中实现,故不需要重写
CreateOrder.prototype.formatParams = function(){
console.log( '格式化新增订单入参' );
};
CreateOrder.prototype.setUrl= function(){
console.log('设置新增订单的请求地址')
}
// 创建新订单
var newOrder = new CreateOrder();
newOrder.submitOrder();
// 更新订单
var UpdateOrder = function(){};
UpdateOrder.prototype = new Order();
UpdateOrder.prototype.formatParams = function(){
console.log( '格式化更新订单入参' );
};
UpdateOrder.prototype.setUrl= function(){
console.log('设置更新订单的请求地址')
}
var editOrder = new UpdateOrder();
editOrder.submitOrder();
通过这种设计模式实现的表单提交,可以避免重复检查表单信息、刷新列表的代码,也可以避免使用判断语句进行条件判断,缺点也是明显的,开发时需要仔细对照不同子类的区别来做好父类的抽象提取,需要事先定义好各种构造函数。
**小结:**发模板方法模式从大方面来看可以用作架构设计时使用,架构师搭建好项目的骨架,程序员往骨架中填空实现。因为模板方法模式是固定了子类的执行顺序,因此我们也可以在模板内加入hook(钩子方法)对每个步骤进行隔离和拦截。一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把 这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这 部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改 动抽象父类以及其他子类,这也是符合开放-封闭原则的。