请介绍一下装饰者模式,并实现?

439 阅读3分钟

什么是装饰者模式

其实很简单,在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活,还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;另一方面,继承这种功能复用方式通常被称为“白箱复用”,“白箱”是相对可见性而言的,在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性。

使用继承还会带来另外一个问题,在完成一些功能复用的同时,有可能创建出大量的子类,使子类的数量呈爆炸性增长。

举个栗子

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

不多说废话了,来点代码:

比如说现在我要去做写一个游戏,里面有个英雄,叫鲁班,然后我要去写它的技能:

class luban {
  fire(){
    console.log('这里是基础伤害');
  }
}

所以我们会写个class,里面有一个发射的方法,只负责基础伤害
然后当鲁班到1级的时候,会解锁第一技能,所以这时候需要对鲁班进行增强,但是如果用继承的方式的话,那么子类会比较多,这时候就可以用到装饰者了:

class firstSkill{
  constructor(luban){
    this.luban = luban;
  }
  fire(){
    this.luban.fire();
    console.log('发射手雷');
  }
}

在 firstSkill 这个类里,我直接传进去一个对象,然后实现了一个一模一样的 fire 方法,于是乎,我们就可以这么去使用了:

var luban1 = new luban;
    luban1 = new firstSkill(luban1);
    luban1.fire(); //这里是基础伤害  发射手雷

现在看上去还是创建了一个对象 luban1,但是注意,luban1被赋值了两次,第二次是 new 的 firstSkill,并且把前面 new 出来的 luban1 传进去了,所以最后 luban1 在调用 fire 方法的时候,会同时具有基础伤害以及第一技能。

这样子,我们就用了一个装饰者 firstSkill 来增强 luban,并且不是用继承,而且也不会破坏原来的 luban

然后装饰者模式还有一种写法是这样的:

let luban = {
  fire:function(){
    console.log('这里是基础伤害');
  }
}

let firstSkill = function(){
  console.log('发射手雷');
}

let fire1 = luban.fire;

luban.fire = function(){
  fire1();
  firstSkill();
}

luban.fire();

这个用的是地址转存的方式,其实怎么写都可以,只要不违背核心思想就OK

实际案例

比较常见的应用场景比如说 window.onload

在我们开发大一点的项目的时候,我们不确定 window.onload 方法别人有没有使用过,而且我们需要在 onload 的时候,做一些事情,又不想覆盖原有的 onload 事件怎么办呢?

其实通过装饰者模式很简单就可以实现:

window.onload = function(){
  console.log(1);
}

let onload = window.onload;
window.onload = function(){
  onload();
  console.log('这里是新增的业务代码');
}

直接把原有的事件转存一下就可以了

高级一点的写法还可以这样:

Function.prototype.before = function(beforeFn){
  let _this = this;
  return function(){
    beforeFn.apply(this,arguments);
    return _this.apply(this,arguments);
  }
};

直接在 function 的原型链上去进行拓展

这样的话用起来就比较爽了:

window.onload = function(){
  console.log('加载完成了');
}

window.onload = window.onload.before(()=>{
  console.log('onload触发之前执行一件事情');
})

//onload触发之前执行一件事情  加载完成了

甚至还可以链式操作:

window.onload = function(){
  console.log('加载完成了');
}

window.onload = window.onload.before(()=>{
  console.log('onload触发之前执行一件事情');
}).before(()=>{
  console.log('onload2');
});

用来去拓展其他方法也是OK的:

<div id="btn">asiodais</div>

document.querySelector = document.querySelector.before(function(){
  console.log('查找元素之前');
})

let btn = document.querySelector('#btn');
console.log(btn);

//查找元素之前  <div id="btn">asiodais</div>

OK 这就是装饰者模式啦,有什么问题大家赶紧甩上来吧~