设计模式-装饰者 学习笔记

模拟传统面向对象预研的装饰模式
首先要说明的是,作为一门解释执行的语言,给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');

你可能感兴趣的:(前端学习总结,读书笔记)