前言
模板方式模式是一种严重依赖“抽象类”的设计模式,我们都知道在JavaScript中并没有真正的实现“类”这个概念。但这不妨碍我们利用这个设计模式的核心思路来优化我们的代码,接下来我们就一起来了解一下吧。
设计思路
我们以冲咖啡和茶作为例子,假如冲一杯咖啡的操作过程是:
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒进杯子里
- 加糖和牛奶
大致代码如下:
class Coffee{
constructor(){
this.init();
}
init(){
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugarAndMilk();
}
boilWater(){}
brewCoffeeGriends(){}
pourInCup(){}
addSugarAndMilk(){}
}
而冲茶的步骤则是:
- 把水煮沸
- 用沸水浸泡茶叶
- 把茶水倒进杯子
- 加柠檬
代码如下
class Coffee{
constructor(){
this.init();
}
init(){
this.boilWater();
this.steepTeaBag();
this.pourInCup();
this.addLemon();
}
}
抽离公共点
观察上述2个例子,我们可以看的其实这两个动作是有共同点的。他们总的来说都是:
- 把水煮沸
- 用沸水冲饮料
- 把饮料倒入杯子
- 加配料
抽象类
根据这个规律,我们可以得到一个新的概念——“冲饮料”。而这个“饮料”就是一个抽象类。我们可以通过代码实现:
class Beverage{
constructor(){
this.init();
}
init(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
}
模板方法
我们现在有了抽象类,那么当我们需要一个具体的“饮料时”,只要再实例化一个具体的对象,让他继承于抽象类就可以了。
class Coffee extends Beverage{
constructor(){
super();
}
// 让具体类覆盖抽象类的方法
brew(){}
addCondiments();
}
这样当我们实例化 Coffee的时候,仍然会调用调用Beverage的init方法,但brew和addConditments已经被替换了。这时父类Beverage的init方法就是一个模板方法。他作用就是来规范操作的步骤流程。
JavaScript中实现
这个设计模式在JavaScript中最大的问题还是缺少“抽象类”,在java中,由于强类型的关系,开发者很容易可以在编码时,就知道具体类需要复写哪些方法。但在JavaScript中,由于“动态类型”的关系,实现上是无法保证这一点的。因此在写代码时,我们需要人为地加入一些提示代码。
class Beverage{
constructor(){
this.init();
}
init(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
brew(){
throw new Error('子类需要重写brew方法');
}
addCondiments(){
throw new Error('子类需要重写addCondiments方法');
}
}
通过这种方式,我们就能及时地发现问题。当然这样最大的缺点是,这段代码需要在运行时才会抛错。如果想要在开发阶段就能发现错误,可以考虑使用typescript,它提供了抽象类的写法,以及编写代码时可以静态分析出错误。
设计场景
模板方法模式的最大优势是能在代码功能设计之初就定好逻辑流程。更多的情况是由架构师来设计大体结构,然后开发者来填充具体每个步骤的逻辑。
// 代码设计
class Beverage{
init(){
this.step1();
this.step2();
this.step3();
this.step4();
}
}
// 开发者填充具体逻辑
class Coffee extends Beverage{
step1(){}
step2(){}
step3(){}
step4(){}
}
总结
模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放-封闭原则的。
参考
《JavaScript设计模式与开发实践》—— 曾探