“这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战”
前言
在面试高级前端时,往往会遇到一些关于设计模式的问题,每次都回答不太理想。恰逢8月更文挑战的活动,准备用一个月时间好好理一下关于设计模式方面的知识点,给自己增加点面试的底气。
定义
模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。
在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。
子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
使用场景
在一些平行的子类中,各子类之间有很多相同的行为,但是之间也有不同的行为,相同和不同的行为都混合在各个子类中实现。可以用模板方法模式来优化一下,把子类实现中的相同部分移到父类中,而将不同的部分留待子类来实现。
头疼的抽象类
首先要说明的是,模板方法模式是一种严重依赖抽象类的设计模式。JavaScript 在语言层面并没有提供对抽象类的支持,我们也很难模拟抽象类的实现。我们要在没有抽象类时所做出的让步和变通。
抽象类的作用
抽象类是在 Java 中的概念,在 Java 中,类分为两种,一种为具体类,另一种为抽象类。具体类可以被实例化,抽象类不能被实例化。由于抽象类不能被实例化,如果写了一个抽象类,那么这个抽象类一定是用来被某些具体类继承的。
用一个生活的场景来理解。比如我们去便利店想买一瓶饮料,我们不能直接跟老板说:“来一瓶饮料。”如果我们这样说了,那么老板接下来肯定会问:“要什么饮料?”饮料只是一个抽象类,只有当我们真正明确了的饮料类型之后,才能得到一瓶咖啡、绿茶、红茶、可乐等等。
抽像方法和具体方法
抽象方法被声明在抽象类中,抽象方法并没有具体的实现过程,是一些“哑”方法。当子类继承了这个抽象类时,必须重写父类的抽象方法。除了抽象方法之外,如果每个子类中都有一些同样的具体实现方法,那这些方法也可以选择放在抽象类中,这可以节省代码以达到复用的效果,这些方法叫作具体方法。当代码需要改变时,我们只需要改动抽象类里的具体方法就可以了。
解决 JavaScript 对抽像类的不支持
JavaScript 并没有从语法层面提供对抽象类的支持。抽象类的第一个作用是隐藏对象的具体类型,由于 JavaScript 是一门“类型模糊”的语言,隐藏对象的类型在 JavaScript 中并不重要。
所以当我们在 JavaScript 中使用原型继承来模拟 Java 的类式继承时,并没有编译器帮助我们进行任何形式的检查,我们也没有办法保证子类会重写父类中的“抽象方法”。
有一个非常有用的办法。那就是在抽像类的方法中抛出一个错误。
class Extraction{
constructor(){
}
a(){
throw new Error( '子类必须重写 a 方法' );
}
}
虽然在编写代码时不会报错,但是在运行代码时会报错,只是这个报错有点晚而已。
实现一个简单的模板方法模式
用一个最经典的例子来介绍。咖啡和茶。
先用程序来实现冲咖啡的过程:
class Coffee{
boilWater(){
console.log( '把水煮沸' );
}
brewCoffeeGriends(){
console.log( '用沸水冲泡咖啡' );
}
pourInCup(){
console.log( '把咖啡倒进杯子' );
}
addSugarAndMilk(){
console.log( '加糖和牛奶' );
}
init(){
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugarAndMilk();
}
}
const coffee = new Coffee();
coffee.init();
再用程序来实现一下泡茶的过程。
class Tea{
boilWater(){
console.log( '把水煮沸' );
}
steepTeaBag(){
console.log( '用沸水浸泡茶' );
}
pourInCup(){
console.log( '把茶水倒进杯子' );
}
addLemon(){
console.log( '加柠檬' );
}
init(){
this.boilWater();
this.steepTeaBag();
this.pourInCup();
this.addLemon();
}
}
const tea = new Tea();
tea.init();
比较这两段程序,会发现泡茶和冲咖啡的步骤其实差不多,Coffee
类和Tea
类中有相同的行为,但是具体上还是有些差异,可以利用模板方法模式,构造一个抽像类Beverage
,把Coffee
类和Tea
类中的行为进行分离出共同点(抽像)后移到抽像类Beverage
中,并将其作为他们的父类。
泡茶 | 冲咖啡 | 抽像处理 | 把水煮沸 | 把水煮沸 | 把水煮沸 | 用沸水冲泡咖啡 | 用沸水浸泡茶叶 | 用沸水浸泡原料 | 把咖啡倒进杯子 | 把茶水倒进杯子 | 把饮料倒进杯子 | 加糖和牛奶 | 加柠檬 | 加配料 |
---|
我们先进行抽像,对比一下煮茶和煮咖啡过程
泡茶 | 冲咖啡 | 把水煮沸 | 把水煮沸 | 用沸水冲泡咖啡 | 用沸水浸泡茶叶 | 把咖啡倒进杯子 | 把茶水倒进杯子 | 加糖和牛奶 | 加柠檬 |
---|
可以发现
- 原料不同。一个是咖啡,一个是茶,但我们可以把它们都抽象为“饮料”。
- 泡的方式不同。咖啡是冲泡,而茶叶是浸泡,我们可以把它们都抽象为“泡”。
- 加入的调料不同。一个是糖和牛奶,一个是柠檬,但我们可以把它们都抽象为“配料”。
经过抽象之后,不管是冲咖啡还是泡茶,我们都能整理为下面四步:
- 把水煮沸
- 用沸水冲泡饮料
- 把饮料倒进杯子
- 加配料
接下来创建一个泡一杯饮料的抽像类Beverage
class Beverage{
constructor(){}
boilWater(){
console.log( '把水煮沸' );
}
brew(){// 用沸水冲泡饮料,空方法,应该由子类重写
throw new Error( '子类必须重写 brew 方法' );
}
pourInCup(){// 把饮料倒进杯子,空方法,应该由子类重写
throw new Error( '子类必须重写 pourInCup 方法' );
}
addCondiments(){// 加配料,空方法,应该由子类重写
throw new Error( '子类必须重写 addCondiments 方法' );
}
init(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}
}
后面冲咖啡和泡茶的过程就可以继承抽像类Beverage
来实现。
class Coffee extends Beverage{
constructor(){
super();
}
boilWater(){
console.log( '把水煮沸' );
}
brew(){
console.log( '用沸水冲泡咖啡' );
}
pourInCup(){
console.log( '把咖啡倒进杯子' );
}
addCondiments(){
console.log( '加糖和牛奶' );
}
}
const coffee = new Coffee();
coffee.init();
以上就实现了一个最简单的模板方式模式,其关键还是将子类中的方法进行抽像。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!