装饰器模式

109 阅读2分钟

面向对象增加功能的问题

在我们传统的面向对象开发中,给对象添加功能时,我们往往会采用继承的方式,继承虽然能够解决问题,但是随之也带来一些问题:

  • 父类和子类存在强耦合的关系,当父类改变时,子类也会跟着改变;
  • 子类需要知道父类中的细节,从而进行复用或重写,这样其实破坏了封装性;
  • 继承的方式,可能会创建出大量子类。

装饰器的目的就是在不改变原有的类的基础上,为其在运行期间动态的添加职责。

装饰器模式

在不改变自身对象的基础上,可以给对象附加更多的功能;装饰模式能够在不使用创造更多子类的状况下,将对象的功能加以扩展。

//原有对象
function show() {
    console.log("原有功能")
}
//装饰器1
function logger(show) {
    return function(msg) {
        const result = show.apply(this, arguments);
        console.log(msg)
        return result
    }
}
// 装饰器2
function fn2(show) {
    return function() {
        const result = show.apply(this, arguments);
        console.log('功能2')
        return result
    }
}
let log = new logger(new fn2(show))
log('log*****')
// 原有功能
// 功能2
// log*****

利用aop的思想,增加before/after两个装饰方法

Function.prototype.before = function (beforeFn) {
  const _self = this;
  return function () {
    beforeFn.apply(this, arguments); // 增加的功能
    return _self.apply(this, arguments); // 原有功能
  }
}

Function.prototype.after = function (afterFn) {
  const _self = this;
  return function () {
    const result = _self.apply(this, arguments); // 原有功能
    afterFn.apply(this, arguments);
    return result;
  }
}

let func = function () {
  console.log('原有功能');
}

func = func.after(function() {
  console.log('新增功能1');
}).after(function() {
  console.log('新增功能2');
})

func();
// 原有功能
// 新增功能1
// 新增功能2

优点

  • 可以动态的给原对象添加功能,'即用即付',非常灵活。
  • 添加新功能的同时不会修改原对象,符合开闭原则。
  • 装饰对象与原对象松耦合,易于维护。

缺点

  • 定义过多的装饰类,会增加系统的复杂性,增加调试的难度。

应用场景:

vue数组变异方法、react高阶组件、原生数组filter、map、reduce方法等等

vue数组变异方法

在保留原生数组方法的基础上,增加响应式功能

methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args) //原生数组方法
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})