【JavaScript设计模式】装饰器模式

装饰器模式

装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

这种给对象动态地增加职责的方式称为装饰者(decorator)模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式,比如天冷了就多穿一件外套,需要飞行时就在头上插一支竹蜻蜓,遇到一堆食尸鬼时就点开 AOE(范围攻击)技能。

优点

  • 装饰类和被装饰类都只关心自身的核心业务,实现了解耦。
  • 方便动态的扩展功能,且提供了比继承更多的灵活性。

缺点

  • 多层装饰比较复杂。
  • 常常会引入许多小对象,看起来比较相似,实际功能大相径庭,从而使得我们的应用程序架构变得复杂起来

举例:

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()

该例中,我们写了一个Decorator装饰器类,它重写了实例对象的create方法,给其方法新增了一个createShell(),因此最后为其输出结果进行了装饰。

例子2:

生活中的例子: 天气冷了, 就添加衣服来保暖;天气热了, 就将外套脱下;这个例子很形象地含盖了装饰器的神韵, 随着天气的冷暖变化, 衣服可以动态的穿上脱下。

let wear = function() {
    
    
  console.log('穿上第一件衣服')
}

const _wear1 = wear

wear = function() {
    
    
  _wear1()
  console.log('穿上第二件衣服')
}

const _wear2 = wear

wear = function() {
    
    
  _wear2()
  console.log('穿上第三件衣服')
}

wear()

// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

这种方式有以下缺点: 1: 临时变量会变得越来越多;2: this 指向有时会出错


AOP 装饰函数

Function.prototype.before = function (beforefn) {
    
    
  var __self = this; // 保存原函数的引用
  return function () {
    
     // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply(this, arguments); // 执行新函数,且保证this不被劫持,新函数接受的参数也会被原封不动地传入原函数,新函数在原函数之前执行
    return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,并且保证this不被劫持
  }
}

Function.prototype.after = function (afterfn) {
    
    
  var __self = this;
  return function () {
    
    
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};

Function.prototype.before接受一个函数当作参数,这个函数即为新添加的函数,它装载了新添加的功能代码。

接下来把当前的this保存起来,这个this指向原函数,然后返回一个“代理”函数,这个“代理”函数只是结构上像代理而已,并不承担代理的职责(比如控制对象的访问等)。它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行(前置装饰),这样就实现了动态装饰的效果。

我们注意到,通过Function.prototype.apply来动态传入正确的this,保证了函数在被装饰之后,this不会被劫持。

Function.prototype.after的原理跟Function.prototype.before一模一样,唯一不同的地方在于让新添加的函数在原函数执行之后再执行。

用AOP装饰函数实现上面的效果:

Function.prototype.before = function (beforefn) {
    
    
  var __self = this; // 保存原函数的引用
  return function () {
    
     // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply(this, arguments); // 执行新函数,且保证this不被劫持,新函数接受的参数
    // 也会被原封不动地传入原函数,新函数在原函数之前执行
    return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
    // 并且保证this不被劫持
  }
}

Function.prototype.after = function (afterfn) {
    
    
  var __self = this;
  return function () {
    
    
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};


const wear1 = function() {
    
    
  console.log('穿上第一件衣服')
}

const wear2 = function() {
    
    
  console.log('穿上第二件衣服')
}

const wear3 = function() {
    
    
  console.log('穿上第三件衣服')
}

const wear = wear1.after(wear2).after(wear3)
wear()

// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

值得注意的是,因为函数通过Function.prototype.before或者Function.prototype.after被装饰之后,返回的实际上是一个新的函数,如果在原函数上保存了一些属性,那么这些属性会丢失。代码如下:

var func = function(){
    
    
  console.log( 1 );
}
func.a = 'a';

func = func.after( function(){
    
    
  console.log( 2 );
});

console.log( func.a );   // 输出:undefined

猜你喜欢

转载自blog.csdn.net/weixin_52834435/article/details/127340882