ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] # 模板方法模式 模板方法模式是一种只需使用继承就可以实现的非常简单的模式。 模板方法模式由两部分构成: - 抽象父类:封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。 - 具体实现的子类:通过基础抽象父类,也继承了整个算法结构,可以选择重写父类的方法。 假如一些平行的子类,各个子类直接有一些相同的行为,也有一些不同的行为。相同的行为可能会重复出现在各个子类之间,那这些相同的行为可以被搬到另一个单一的地方,模板方法模式就是为了解决这个问题而生的。 ```javascript var Beverage = function(); Beverage.prototype.boilWater = function(){ // 相同的方法 ... }; 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 = new Beverage(); Coffee.prototype.brew = function(){ ... }; Coffee.prototype.pourInCup = function(){ ... }; Coffee.prototype.addCondiments = function(){ ... } var coffee = new Coffee(); conffee.init(); ``` 在上面的例子中,`Beverage.prototype.init`才是模板方法模式。该方法封装了子类的算法框架,它作为一个算法模板,指导子类以何种顺序去执行哪些方法。 ## 抽象类 模板方法模式是一种严重依赖抽象类的设计模式。在`Java`中有两种类,一种是具体类,一种是抽象类。具体类可以被实例化,抽象类不能被实例化。就像说:我要一杯饮料,但是随后还会被问到要什么饮料。而是要具体说咖啡。 抽象方法被声明在抽象类中,抽象方法并没有具体的实现过程,是一些“哑”方法。当子类继承了这个抽象类时,必须重写父类的抽象方法。 如果每个子类中都有一些同样的具体实现方法,那这些方法也可以选择放在抽象类中,这些方法就被称为具体方法。 ## JavaScript没有抽象类的缺点和解决方案 Java中编译器会保证子类会重写父类中的抽象方法,JavaScript中却没有进行这些检查工作。编写代码时得不到任何形式的警告,完全寄托于程序员的记忆力和自觉性是很危险的。 有两种解决方案 - 用鸭子类型来模拟接口类型检查,以确保子类中确实重写了父类的方法。但会带来不必要的复杂性。 - 抽象父类中的方法直接抛出异常。实现简单,付出的额外代价很少。但是得到错误信息时间太靠后。 ```javascript Beverage.prototype.brew = function(){ // 相同的方法 throw new Error('子类必须重写brew方法') }; ``` ## 使用场景 大的方面就是架构师用于搭建项目的框架,程序员继承框架开始填空。 比如在Web开发中的UI组件。(1)初始化一个div容器;(2)通过ajax请求拉取相应的数据;(3)把数据渲染到div容器里面,完成组件的构造;(4)通知用户组件渲染完成。任何组件的构建(1)(4)相同。把4个方法都抽象到父类的模板方法,提供(1)(4)的具体实现。子类继承后,重写(2)(3); ## 钩子方法 隔离变化的常见手段。 ## 好莱坞原则 允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件。也就是“别调用我们,我们会调用你”。 当我们用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类只负责一些设计上的细节。 ## 真的需要继承吗 ```javascript var Beverage = function(param){ var boilWater = function(){...}; var brew = param.brew || function(){...}; var pourInCup = param.pourInCup || function(){...}; var F = function(){}; F.prototype.init = function(){ boilWater(); brew(); pourInCup(); } return F; }; // 创建子类 var Coffee = Beverage({ brew: function(){...},// 重写 pourInCup: function(){...} }); var coffee = new Coffee(); coffee.init(); ``` 该模板方法依然封装了子类的算法框架。