模拟传统面向对象预研的装饰模式
首先要说明的是,作为一门解释执行的语言,给js中的对象动态添加或者改变职责是一件再简单不过的事情,
var obj = {
name : 'testName'
}
obj.name = 'testName2'
虽然这种做法改动了对象本身,跟传统定义中的装饰者模式并不一样,但是却更加符合js语言特色
**实际上传统面向对象语言中的装饰者模式在js中的适用场景并不多。**下面的例子是模拟传统面向对象语言,实现装饰者模式
需求:假如我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可用升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时,可用发射导弹,升到第三级时可用发射原子弹。
var Plane = function(){};
Plane.prototype.fire = function(){
console.log( '发射普通子弹' );
}
var MissileDecorator = function( plane ){
this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '发射导弹' );
}
var AtomDecorator = function( plane ){
this.plane = plane;
}
AtomDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '发射原子弹' );
}
var plane = new Plane();
plane = new MissileDecorator( plane );
plane = new AtomDecorator( plane );
plane.fire();
//输出:发射普通子弹 发射导弹 发射原子弹
这种给对象动态增加职责的方式,并没有改变对象自身,而是将对象放入另一个对象中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(file方法),当请求达到链中某个对象时,这个对象会执行自身的操作,随后又把请求转发给链中的下一个对象。
上面的例子是使用"类"来实现装饰模式。下面是直接改变对象或者对象的某个方法。
var plane = {
fire: function(){
console.log( '发射普通子弹' );
}
}
var missileDecorator = function(){
console.log( '发射导弹' );
}
var atomDecorator = function(){
console.log( '发射原子弹' );
}
var fire1 = plane.fire;
plane.fire = function(){
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2();
atomDecorator();
}
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
这个例子中,变量fire1 fire2分别保存不同的fire方法,实现对象函数引用的调用。
上述两个例子是模拟传统编程语言 实现装饰者模式。
下面我们来装饰函数
在js中,我们可以很方便的给某个对象扩展属性或者方法,但是却很难再不改动某个函数源代码的情况下,给该函数添加一些额外的功能。
想要给函数添加一些功能,最简单粗暴的方法就是直接改写该函数,但是这是最差的方法,违反了开放-封闭原则,如下:
var a = function(){
alert(1);
}
//=================>
var a = function(){
alert(1);
alert(2);
}
但是实际上,我们很多时候是不想去碰原函数的,也许是原函数是其他同事编写,里面实现十分杂乱。现在需要不改写函数源代码的情况下,能给函数增加功能,
常见改写如下:
var a = function(){
alert(1);
}
var _a = a;
a = function(){
_a()
alert(2)
}
这是工作中常见的做法,避免a的逻辑被覆盖。还有onload函数,常用到这种做法。比如现在想给window绑定onload事件,但是不确定这个事件之前是否已绑定,为了避免覆盖之前的window.onload函数中的行为,我们一般都会保存好原先的window.onload,把它放入新的window.onload里执行:
window.onload = function(){
alert(1);
}
//保存之前的onload逻辑
var _onload = window.onload || function(){};
window.onload = function(){
_onload();
alert(2);
}
这种代码是符合开放-封闭原则,在新增功能的同时,不改变原来的onload代码,但是也还是存在下面两个问题
- 必须要维护_onload这个变量,假如函数的装饰链过程,或者要装饰的函数变多,中间这些变量的数量也会越来越多
- this劫持的问题,在window.onload的例子中没有这烦恼,是因为调用普通函数_onload时,this也指向window,假如吧window.onload换成document.getElementById,如下
var _getElementById = document.getElementById;
document.getElementById = function(id){
alert(1);
return _getElementById(id) //(1)
}
var button = document.getElementById('button');