JavaScript设计模式中的模版方法模式

模版方法模式

模版方法模式通过封装提高系统扩展性,把不变的逻辑抽象到父类,子类继承父类方法,子类逻辑方法是可变的。通过增加新的子类,就可以增加新的功能,并不需要改动抽象父类以及其他子类

只需使用继承就可以实现,把相同的逻辑抽离到单一的地方,模版方法就是为解决这个问题而生的。模版方法中,子类实现中的相同部分被上移到父类中,而将不同的部分留在子类实现。

模版方法由两部分结构组成:

  1. 第一部分抽象父类:通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法执行顺序。
  2. 第二部分是具体的实现子类:继承这个抽象类,也继承整个算法结构,并且可以选择重写父类的方法;

抽象类

  1. 模版方法模式严重依赖抽象类的设计模式
  2. 类分为两种:具体类和抽象类
    1. 具体类: 可以被实例化,也就是指向具体对象,比如可乐就是具体类
    2. 抽象类:不能被实例化,是被某些具体类继承的。比如饮料就可以当成抽象类,饮料分为很多种。
      1. 作用:为子类定义这些公共接口(方法)

抽象方法和具体方法

  1. 抽象方法被声明在抽象类中,抽象方法并没有具体的实现方法,使一些 哑 方法。比如 brew、pour 都是抽象方法。当子类继承了这个抽象类时,必须重写父类的抽象方法。

JavaScript 没有抽象类的缺点和解决方案

  1. JavaScript 没有从语法层面提供对抽象类的支持。抽象类的第一个作用时隐藏对象的具体类型,由于 JavaScript 是一门“类型模糊”的语言,所以隐藏对象的类型在 JavaScript 中并不重要。

  2. 在子类中如果我们忘记写 x 方法,那么请求会顺着原型链从父类找到对应的方法 x,而父类中 x 方法只是一个空方法。

    1. 在 Java 中编译器会保证重写父类的抽象方法。但 JavaScript 没有进行这些检查工作,得不到任何警告。
  3. 解决方法

    1. 鸭子类型 [https://zhuanlan.zhihu.com/p/406675760] 模拟接口检查,以便确保子类中确实重写了父类的方法。但模拟接口需要添加一些跟业务无关的代码,带来不必要的复杂性
    2. 在父类定义方法的时候,抛出一个异常
    Father.prototype.brew = function () {
      throw new Error("必须重写 brew 方法");
    };
    
     ```javascript
     /**
      * 泡咖啡
      * 1. 把水煮沸
      * 2. 用沸水冲泡咖啡
      * 3. 把咖啡倒进杯子
      * 4. 加糖和牛奶
      */
    
     const Coffee = function () {};
     Coffee.prototype.boilWater = function () {
       console.log("把水煮沸");
     };
    
     Coffee.prototype.brewCoffeeGriends = function () {
       console.log("用沸水冲泡咖啡");
     };
    
     Coffee.prototype.pourInCup = function () {
       console.log("把咖啡倒进杯子");
     };
    
     Coffee.prototype.addSugarAndMilk = function () {
       console.log("加糖和牛奶");
     };
    
     Coffee.prototype.init = function () {
       this.boilWater();
       this.brewCoffeeGriends();
       this.pourInCup();
       this.addSugarAndMilk();
     };
    
     const Coffee1 = new Coffee();
     Coffee1.init();
    
     /**
      * 泡茶
      * 1. 把水煮沸
      * 2. 用沸水冲泡茶
      * 3. 把茶倒进杯子
      * 4. 加柠檬
      */
    
     const Tea = function () {};
     Tea.prototype.boilWater = function () {
       console.log("把水煮沸");
     };
    
     Tea.prototype.brewCoffeeGriends = function () {
       console.log("用沸水冲泡茶");
     };
    
     Tea.prototype.pourInCup = function () {
       console.log("把茶倒进杯子");
     };
    
     Tea.prototype.addSugarAndMilk = function () {
       console.log("加柠檬");
     };
    
     Tea.prototype.init = function () {
       this.boilWater();
       this.brewCoffeeGriends();
       this.pourInCup();
       this.addSugarAndMilk();
     };
    
     const Tea1 = new Tea();
     Tea1.init();
    
     /**
      * 分离出共同点
      * 不同点:
      *    原料不同:咖啡/茶
      *    泡的方式不同,咖啡冲泡,茶侵泡。简称为泡
      *    加入调料不同,糖牛奶/柠檬,简称为调料
      */
    
     class CoffeTea {
       constructor() {}
       boilWater() {
         console.log("把水煮沸");
       }
       brew() {}
       pour() {}
       add() {}
       init() {
         this.boilWater();
         this.brew();
         this.pour();
         this.add();
       }
     }
    
     const Coffee2 = new CoffeTea();
     Coffee2.brew = function () {
       console.log(1);
     };
     Coffee2.pour = function () {
       console.log(2);
     };
     Coffee2.add = function () {
       console.log(3);
     };
    
     Coffee2.init();
     ```
    

模版方法模式的使用场景

  1. 从发的方面讲,模版方法模式常被架构师用于搭建项目的框架,架构师定好了框架的骨架,程序员继承框架的结构之后,负责往里面填空。

钩子方法

  1. 通过模版方法模式,我们在父类中封装了子类的算法框架,这些算法框架在正常情况下时适用于大多数子类的。但如果有一些特别的子类,如何让这些子类不受这个约束呢。钩子方法可以解决这个问题,放置钩子函数时隔离变化的一种常见手段,我们在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,子类决定要不要挂钩。

    class bb {
      constructor() {}
      boil() {
        console.log(1);
      }
      add() {
        console.log("add");
      }
      isAdd() {
        return true;
      }
      init() {
        this.boil();
        if (this.isAdd()) {
          this.add();
        }
      }
    }
    
    const bbbb1 = new bb();
    bbbb1.boil = function () {
      console.log("boil");
    };
    
    bbbb1.isAdd = function () {
      return false;
    };
    
    bbbb1.init();
    

你可能感兴趣的:(JavaScript设计模式,javascript,设计模式)