JavaScript设计模式学习笔记(二)——模板方法模式

244 阅读5分钟

**定义:**模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常 在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺 序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。说白了就是通过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(钩子方法)对每个步骤进行隔离和拦截。一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把 这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这 部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改 动抽象父类以及其他子类,这也是符合开放-封闭原则的。