JS 设计模式

541 阅读10分钟

概念

  • 设计模式就是前人总结出来的一套更加容易阅读、维护以及复用的写代码的方式

SOLID 五大设计原则

  • 前端 js 用的最多的是 S、O 原则

S(Single responsibility principle)——单一职责原则

  • 一个程序只做一件事
  • 如果功能过于复杂就拆分开,每个部分保持独立

O(Open Closed Principle)——开放封闭原则

  • 面向对象的核心
  • 对扩展开放,对修改封闭
  • 增加需求时,扩展新代码,而非修改已有代码

L(Liskov Substitution Principle, LSP)——李氏置换原则

  • 子类能覆盖父类
  • 父类能出现的地方子类就能出现
  • js 中使用功能较少(弱类型 & 继承使用较少)

I (Interface Segregation Principle)——接口独立原则

  • 保持接口的单一独立
  • js 中没有接口概念(typescript例外)
  • 类似单一职责原则,这里更关注接口

D(Dependence Inversion Principle ,DIP)——依赖倒置原则

  • 面向接口编程,依赖于抽象而不依赖于具体
  • 使用方只关注接口而不关注具体类的实现
  • js 中使用较少(没有接口概念,弱类型)

设计模式的类型

创建型模式(Creational Patterns)

  • 创建型模式就是创建对象的模式,抽象了实例化的过程,即对创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关心创建对象过程中的逻辑
  • 这里有6个具体的创建型模式可供研究,它们分别是:
    • 简单工厂模式(Simple Factory)它不是GoF总结出来的23种设计模式之一
    • 工厂方法模式(Factory Method)
    • 抽象工厂模式(Abstract Factory)
    • 建造者模式(Builder)
    • 原型模式(Prototype)
    • 单例模式(Singleton)

结构型模式(Structural Patterns)

  • 结构型模式就是组装现有的类并设计它们的交互方式,也就是设计对象的结构、继承和依赖关系
  • 对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低
  • 这里有7个具体的结构型模式可供研究,它们分别是:
    • 外观模式/门面模式(Facade门面模式)
    • 适配器模式(Adapter)
    • 代理模式(Proxy)
    • 装饰模式(Decorator)
    • 桥梁模式/桥接模式(Bridge)
    • 组合模式(Composite)
    • 享元模式(Flyweight)

行为型模式(Behavioral Patterns)

  • 行为模式描述了对象和类的模式,以及它们之间的通信模式
  • 行为模式用于识别对象之间常见的交互模式并加以实现,提升它们之间的协作效率
  • 这里有11个具体的行为型模式可供研究,它们分别是:
    • 模板方法模式(Template Method)
    • 观察者模式(Observer)
    • 状态模式(State)
    • 策略模式(Strategy)
    • 职责链模式(Chain of Responsibility)
    • 命令模式(Command)
    • 访问者模式(Visitor)
    • 调停者模式(Mediator)
    • 备忘录模式(Memento)
    • 迭代器模式(Iterator)
    • 解释器模式(Interpreter)

三者之间的区别和联系

  • 创建型模式为其他两种模式使用提供了环境
  • 结构型模式侧重于接口的使用,它做的一切工作都是对象或是类之间的交互,提供一个门
  • 行为型模式顾名思义,侧重于具体行为
  • 总结一句话就是:创建型模式提供生存环境,结构型模式提供生存理由,行为型模式提供如何生存

前端常用的设计模式

创建型模式之工厂模式(Factory Method)

  • 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类,而子类可以重写接口方法以便创建的时候指定自己的对象类型
  • 将 new 操作进行简单的封装,遇到 new 我们就应该考虑是否用工厂模式
  • 相对于复杂的创建对象的过程我们只关心结果,符合开闭原则,扩展性高
  • JQuery 的 $() 就是一个工厂函数,它根据传入参数的不同创建元素或者去寻找上下文中的元素,创建成相应的 jQuery 对象
class jQuery {
    constructor(selector) {
        super(selector)
    }
    add() {
        
    }
  // 此处省略若干API
}
window.$ = function(selector) {
    return new jQuery(selector)
}

创建型模式之建造者模式( Builder pattern)

  • 将一个复杂的对象分解成多个简单的对象来进行构建,将复杂的构建层与表现层分离,使相同的构建过程可以创建不同的表示模式
  • 建造的封装性比较好,创建对象和构建过程解藕,并且很容易扩展
  • 分步创建一个复杂的对象,解藕封装过程和具体创建组件
    function Fangzi(){
        this.woshi = "";
        this.keting = "";
        this.chufang = "";
    }
    
    function Baogongtou(){
        this.jianfangzi = function(gongren){
            gongren.jian_woshi();
            gongren.jian_keting();
            gongren.jian_chufang();
        }
    }

    function Gongren(){
        this.jian_woshi = function(){
            console.log("卧室建好了!");
        }

        this.jian_keting = function(){
            console.log("客厅建好了!");
        }

        this.jian_chufang = function(){
            console.log("厨房建好了!");
        }

        this.wangong = function(){
            var fangzi = new Fangzi();
            fangzi.woshi = "ok";
            fangzi.keting = "ok";
            fangzi.chufang = "ok";
            return fangzi;
        }
    }
    let gongren = new Gongren();
    let baogongtou = new Baogongtou();
    //卧室建好了!
    //客厅建好了!
    //厨房建好了!
    baogongtou.jianfangzi(gongren);
    var my_fangzi = gongren.wangong();
/*
    Fangzi={
           chufang: "ok"
           keting: "ok"
           woshi: "ok"
           }
*/
    console.log(my_fangzi);   

创建型模式之原型模式(Prototype pattern)

  • 创建一个共享的原型,通过拷贝这个原型来创建新的类,用于创建重复的对象带来性能上的提升
  • Object.create() 方法会使用指定的原型对象及其属性去创建一个新的对象。
// 因为不是构造函数,所以不用大写
var someCar = {
    drive: function () { },
    name: '红旗'
};
// 使用Object.create创建一个新车x
var anotherCar = Object.create(someCar);
anotherCar.name = '中华';
  • 如果你希望自己去实现原型模式,而不直接使用Object.create。你可以使用一下代码实现。
var vehiclePrototype = {
    init: function (carModel) {
        this.model = carModel;
    },
    getModel: function () {
        console.log('车辆模具是:' + this.model);
    }
};
function vehicle(model) {
    function F() { };
    F.prototype = vehiclePrototype;
    var f = new F();
    f.init(model);
    return f;
}
var car = vehicle('福特Escort');
car.getModel();

创建型模式之单例模式(Singleton Pattern)

  • 一个类只有一个实例,并提供一个访问它的全局访问点
  • 隐藏 Class 的构造函数,避免多次实例化,通过暴露一个 getInstance() 方法来创建 / 获取唯一实例
  • 优点:
    • 划分命名空间,减少全局变量
    • 增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
    • 且只会实例化一次。简化了代码的调试和维护
  • 缺点:由于单例模式提供的是一种单点访问,所以它有可能导致模块间的强耦合 从而不利于单元测试。无法单独测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试。
  • 登录框、vuex 和 redux 中的 store
// 单例构造器
const FooServiceSingleton = (function () {
  // 隐藏的Class的构造函数
  function FooService() {}
  // 未初始化的单例对象
  let fooService;
  return {
    // 创建/获取单例对象的函数
    getInstance: function () {
      if (!fooService) {
        fooService = new FooService();
      }
      return fooService;
    }
  }
})();
const fooService1 = FooServiceSingleton.getInstance();
const fooService2 = FooServiceSingleton.getInstance();
console.log(fooService1 === fooService2); // true

结构型模式之外观模式(Facade Pattern)

  • 为子系统中的一组接口提供一个统一的高层接口,使子系统更加容易使用
  • 兼容浏览器事件的绑定:
let addMyEvent = function (el, ev, fn) {
    if (el.addEventListener) {
        el.addEventListener(ev, fn, false)
    } else if (el.attachEvent) {
        el.attachEvent('on' + ev, fn)
    } else {
        el['on' + ev] = fn
    }
}; 

结构型模式之适配器模式(Adapter Pattern)

  • 将一个类的接口转化为另外一个接口,以满足用户需求,解决类之间接口不兼容问题
  • 与代理模式类似它提供一个不同的接口,而代理模式提供一模一样的接口
  • 比如你现在正在用一个自定义的 js 库,里面有个根据 id 获取节点的方法 $id()。有天你觉得 jquery 里的 $ 实现得更酷, 但你又不想让你的工程师去学习新的库和语法, 那一个适配器就能让你完成这件事情
$id = function( id ){
    return jQuery( '#' + id );
}

结构型模式之代理模式(Proxy Pattern)

  • 将一个对象的访问交给另外一个代理对象来操作
  • 代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
  • 代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则;
  • HTML 元素事件代理,ES6 的 Proxy
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
    let ul = document.querySelector('#ul');
    ul.addEventListener('click', event => {
        console.log(event.target);
    });
</script>

结构型模式之装饰者模式(Decorator Pattern)

  • 动态的给某一个对象添加一些额外的职责,是一种实现继承的替代方案
  • 在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象
class Cellphone {
    create() {
        console.log('生成一个手机')
    }
}
class Decorator {
    constructor(cellphone) {
        this.cellphone = cellphone
    }
    create() {
        this.cellphone.create()
        this.createShell(cellphone)
    }
    createShell() {
        console.log('生成手机壳')
    }
}
// 测试代码
let cellphone = new Cellphone()
cellphone.create()
console.log('------------')
let dec = new Decorator(cellphone)
dec.create()

结构型模式之桥接模式(Bridge Pattern)

  • 将抽象部分与它的实现部分分离,使他们都可以独立的变化
  • 有助于独立的管理各组成部分,把抽象化与实现化解藕,提高可扩充性
class Color {
    constructor(name){
        this.name = name
    }
}
class Shape {
    constructor(name,color){
        this.name = name
        this.color = color 
    }
    draw(){
        console.log(`${this.color.name} ${this.name}`)
    }
}
//测试
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()

结构型模式之组合模式(Composite Pattern)

  • 用小的子对象来构建更大的对象,同时这些小的子对象也许是由更小的孙对象构成
  • 组合模式将对象组合成树形结构,可以很方便的描述对象 部分-整体 的树形结构,利用对象的多态性统一对待组合对象和单个对象
class TrainOrder {
	create () {
		console.log('创建火车票订单')
	}
}
class HotelOrder {
	create () {
		console.log('创建酒店订单')
	}
}
class TotalOrder {
	constructor () {
		this.orderList = []
	}
	addOrder (order) {
		this.orderList.push(order)
		return this
	}
	create () {
		this.orderList.forEach(item => {
			item.create()
		})
		return this
	}
}
// 可以在购票网站买车票同时也订房间
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()

结构型模式之享元模式(Flyweight Pattern)

  • 主要用于减少创建对象的数量,以减少内存占用和提高性能
  • 享元模式要求将对象的属性划分为内部状态和外部状态(状态在这里通常指属性),其目标是尽量减少共享对象的数量
//雇佣模特
var HireModel = function(sex){
  //内部状态是性别
  this.sex = sex;
};
HireModel.prototype.wearClothes = function(clothes){
  console.log(this.sex+"穿了"+clothes);
};
//工厂模式,负责造出男女两个模特
var ModelFactory = (function(){
  var cacheObj = {};
  return {
    create:function(sex){
      //根据sex分组
      if(cacheObj[sex]){
        return cacheObj[sex];
      } else {
        cacheObj[sex] = new HireModel(sex);
        return cacheObj[sex];
      }
    }
  };
})();
//模特管理
var ModelManager = (function(){
  //容器存储:1.共享对象 2.外部状态
  var vessel = {};
  return {
    add:function(sex,clothes,id){
      //造出共享元素:模特
      var model = ModelFactory.create(sex);
      //以id为键存储所有状态
      vessel[id] = {
        model:model,
        clothes:clothes
      };
    },
    wear:function(){
      for(var key in vessel){
        //调用雇佣模特类中的穿衣服方法。
        vessel[key]['model'].wearClothes(vessel[key]['clothes']);
      }
    }
  };
})();
/*******通过运行时间测试性能**********/
for(var i=0;i<100;i++){
  ModelManager.add('male','第'+i+'款男衣服',i);
  ModelManager.add('female','第'+i+'款女衣服',i);
}
ModelManager.wear();    

行为型模式之模版方法模式(Template Method)

  • 模版方法模式由两部分组成:抽象父类和具体的实现子类
  • 再抽象父类中封装了子类的算法框架,包括实现一些公共方法和封装子类所有方法的执行顺序
  • 子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法
  • 模版方法模式提取了公共代码部分易于维护
class Beverage {
    constructor({brewDrink, addCondiment}) {
        this.brewDrink = brewDrink
        this.addCondiment = addCondiment
    }
    /* 烧开水,共用方法 */
    boilWater() { console.log('水已经煮沸=== 共用') }
    /* 倒杯子里,共用方法 */
    pourCup() { console.log('倒进杯子里===共用') }
    /* 模板方法 */
    init() {
        this.boilWater()
        this.brewDrink()
        this.pourCup()
        this.addCondiment()
    }
}
/* 咖啡 */
const coffee = new Beverage({
     /* 冲泡咖啡,覆盖抽象方法 */
     brewDrink: function() { console.log('冲泡咖啡') },
     /* 加调味品,覆盖抽象方法 */
     addCondiment: function() { console.log('加点奶和糖') }
})
coffee.init() 

行为型模式之观察者模式(Observer Pattern)

  • 通常又被称为发布-订阅者模式或消息机制
  • 被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者
  • 它定义了对象间的一种一对多的依赖关系,只要当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题
  • 场景:DOM事件、Vue 响应式
document.body.addEventListener('click', function() {
    console.log('hello world!');
});
document.body.click()

行为型模式之状态模式(State Pattern)

  • 允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类
  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
  • 一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示
class SuperMarry {
  constructor() {
    this._currentState = []
    this.states = {
      jump() {console.log('跳跃!')},
      move() {console.log('移动!')},
      shoot() {console.log('射击!')},
      squat() {console.log('蹲下!')}
    }
  }
  change(arr) {  // 更改当前动作
    this._currentState = arr
    return this
  }
  go() {
    console.log('触发动作')
    this._currentState.forEach(T => this.states[T] && this.states[T]())
    return this
  }
}
new SuperMarry()
    .change(['jump', 'shoot'])
    .go()                    // 触发动作  跳跃!  射击!
    .go()                    // 触发动作  跳跃!  射击!
    .change(['squat'])
    .go()                    // 触发动作  蹲下!

行为型模式之策略模式(Strategy Pattern)

  • 定义一系列算法,把它们一个个封装起来,并且使他们可以相互替换
  • 目的就是将算法的使用和算法的实现分离开来
  • 一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后 把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。
  • 优点
    • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
    • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
    • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
    • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
  • 缺点
    • 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
    • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
	var strategies = {
		"S": function( salary ){
			return salary * 4;
		},
		"A": function( salary ){
			return salary * 3;
		},
		"B": function( salary ){
			return salary * 2;

		}
	};
	var calculateBonus = function( level, salary ){
		return strategies[ level ]( salary );
	};
	console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
	console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

行为型模式之职责链模式(Chain of Responsibility Pattern)

  • 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
  • 场景:作用域链、原型链
// 请假审批,需要组长审批、经理审批、总监审批
class Action {
    constructor(name) {
        this.name = name
        this.nextAction = null
    }
    setNextAction(action) {
        this.nextAction = action
    }
    handle() {
        console.log( `${this.name} 审批`)
        if (this.nextAction != null) {
            this.nextAction.handle()
        }
    }
}
let a1 = new Action("组长")
let a2 = new Action("经理")
let a3 = new Action("总监")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

行为型模式之命令模式(Command Pattern)

  • 用于将一个请求封装成为对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。
  • 也就是说:该模式旨在将函数的调用,请求和操作封装成为一个单一的对象,然后对这个对象进行一系列的处理。 命令模式中的命令:指的是一个指向某些特定事情的指令。
// 接收者类
class Receiver {
    execute() {
      console.log('接收者执行请求')
    }
  }
  
// 命令者
class Command {  
    constructor(receiver) {
        this.receiver = receiver
    }
    execute () {    
        console.log('命令');
        this.receiver.execute()
    }
}
// 触发者
class Invoker {   
    constructor(command) {
        this.command = command
    }
    invoke() {   
        console.log('开始')
        this.command.execute()
    }
}
// 仓库
const warehouse = new Receiver();   
// 订单    
const order = new Command(warehouse);  
// 客户
const client = new Invoker(order);      
client.invoke()

行为型模式之访问者模式(Visitor Pattern)

  • 针对于对象结构的元素,定义在不改变该对象的前提下访问结构中元素如的新方法。
    var Visitor = (function() {
      return {
        splice: function(){
          var args = Array.prototype.splice.call(arguments, 1)
          return Array.prototype.splice.apply(arguments[0], args)
        },
        push: function(){
          var len = arguments[0].length || 0
          var args = this.splice(arguments, 1)
          arguments[0].length = len + arguments.length - 1
          return Array.prototype.push.apply(arguments[0], args)
        },
        pop: function(){
          return Array.prototype.pop.apply(arguments[0])
        }
      }
    })()
    
    var a = new Object()
    console.log(a.length)
    Visitor.push(a, 1, 2, 3, 4)
    console.log(a.length)
    Visitor.push(a, 4, 5, 6)
    console.log(a.length)
    Visitor.pop(a)
    console.log(a)
    console.log(a.length)
    Visitor.splice(a, 2)
    console.log(a)

行为型模式之备忘录模式(Memento Pattern)

  • 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  • 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
//备忘类
class Memento{
    constructor(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
}
// 备忘列表
class CareTaker {
    constructor(){
        this.list = []
    }
    add(memento){
        this.list.push(memento)
    }
    get(index){
        return this.list[index]
    }
}
// 编辑器
class Editor {
    constructor(){
        this.content = null
    }
    setContent(content){
        this.content = content
    }
    getContent(){
     return this.content
    }
    saveContentToMemento(){
        return new Memento(this.content)
    }
    getContentFromMemento(memento){
        this.content = memento.getContent()
    }
}
//测试代码
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())
editor.setContent('444')
console.log(editor.getContent()) //444
editor.getContentFromMemento(careTaker.get(1))
console.log(editor.getContent()) //333
editor.getContentFromMemento(careTaker.get(0))
console.log(editor.getContent()) //222

行为型模式之迭代器模式(Iterator Pattern)

  • 提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
  • 为遍历不同数据结构的 “集合” 提供统一的接口;能遍历访问 “集合” 数据中的项,不关心项的数据结构
  • ES6 的 Iterator 迭代器
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next();  // { value: 'a', done: false }
iterator.next();  // { value: 'b', done: false }
iterator.next();  // { value: 'c', done: false }
iterator.next();  // { value: undefined, done: true }
// Iterator 实现
let iteratorFun = function(arr) {
    let nextIndex = 0
    return {
        next: function() {
            return nextIndex < arr.length ? {
                value: arr[nextIndex++],
                done: false
            } : {
                value: undefined,
                done: true
            }
        }
    }
}
let it = iteratorFun(arr)
console.log(it.next()) // {value: "a", done: false}
console.log(it.next()) // {value: "b", done: false}
console.log(it.next()) // {value: "c", done: false}
console.log(it.next()) // {value: undefined, done: true}

行为型模式之解释器模式(Interpreter Pattern)

  • 给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子
  • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
  • 执行效率较低,在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度慢。对于复杂的文法比较难维护
class Context {
    constructor() {
      this._list = []; // 存放 终结符表达式
      this._sum = 0; // 存放 非终结符表达式(运算结果)
    }
    get sum() {
      return this._sum;
    }
    set sum(newValue) {
      this._sum = newValue;
    }
    add(expression) {
      this._list.push(expression);
    }
    get list() {
      return [...this._list];
    }
  }
  class PlusExpression {
    interpret(context) {
      if (!(context instanceof Context)) {
        throw new Error("TypeError");
      }
      context.sum = ++context.sum;
    }
  }
  class MinusExpression {
    interpret(context) {
      if (!(context instanceof Context)) {
        throw new Error("TypeError");
      }
      context.sum = --context.sum;
    }
  }
  /** 以下是测试代码 **/
  const context = new Context();
  // 依次添加: 加法 | 加法 | 减法 表达式
  context.add(new PlusExpression());
  context.add(new PlusExpression());
  context.add(new MinusExpression());
  // 依次执行: 加法 | 加法 | 减法 表达式
  context.list.forEach(expression => expression.interpret(context));
  console.log(context.sum);