装饰者模式
定义 :
装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。
在不改变对象自身的基础上,在程序运行期间给对象动态地添加一些额外职责
特点 :
可以动态的给某个对象添加额外的职责,而不会影响从这个类中派生的其它对象;
作用:
装饰者模式的作用就是为对象动态的加入某些行为。
为什么要使用装饰者模式
在传统面向对象语言中,为对象添加功能常使用继承
但是继承有很多缺点:
超类子类强耦合,超类改变导致子类改变
超类内部细节对子类可见,破坏了封装性
完成功能复用同时,可能会创造大量子类
举例:
var person = {
name: 'payen',
sex: 'male'
}
person.age = '20';
装饰者模式是在不改变对象自身的基础上
而我们改变了原对象 ,所以上面这个例子不是装饰器
示例:
/*模拟传统语言的装饰者
装饰者模式将一个对象嵌入到另一个对象之中,
实际上相当于这个对象被另一个对像包装起来,形成一条包装链。
请求随着这条包装链依次传递到所有的对象,每个对象都有处理这条请求的机会。
*/
//原始的飞机类
var Plan = function () {
};
Plan.prototype.fire = function () {
console.log('发射普通子弹');
};
//装饰类
var MissileDecorator = function (plan) {
this.plan = plan;
};
MissileDecorator.prototype.fire = function () {
this.plan.fire();
console.log('发射导弹!');
};
var plan = new Plan();
plan = new MissileDecorator(plan);
plan.fire();
这样给对象动态的增加职责的方式就没有改变对象自身
一个对象放入另一个对象
形成了一条装饰链(一个聚合对象)
而上面的shot和track也就是是装饰者、装饰函数
当函数执行时,会把请求转给链中的下一个对象
函数功能扩展
在JavaScript中,很容易给对象扩展属性与方法
但是却不容易给函数扩展额外功能,除非改函数源码
但是改写函数违反了开放-封闭原则
var foo = function(){
console.log(1);
}
//改为
var foo = function(){
console.log(1);
console.log(2);//增
}
一个常用的方法就是缓存函数引用,改写函数
var foo = function(){
console.log(1);
}
//改为
var foo = function(){
console.log(1);
}
var _foo = foo;
foo = function(){
_foo();
console.log(2);
}
但是这样写还是存在问题
*要维护额外的中间变量(_foo),如果装饰链过长,中间变量就会越来越多
可能会存在this被劫持问题*
关于this劫持问题,看下面的例子:
var getId = document.getElementById;
document.getElementById = function(ID){
console.log(1);
return getId(ID);
}
document.getElementById('demo');
因为使用 document.getElementById 的时候
内部有this引用,而这个this期望指向的是document
但是 getId 在获取了 document.getElementById 引用后
this就指向了window,导致抛出错误
为了让this正确指向document
我们可以做出修改:
var getId = document.getElementById;
document.getElementById = function(ID){
console.log(1);
return getId.call(document, ID);
}
document.getElementById('demo');
AOP装饰函数
AOP(Aspect Oriented Programming)面向切面编程
把一些与核心业务逻辑无关的功能抽离出来
再通过“动态织入”方式掺入业务逻辑模块
与业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等等
好处也很明显,保证了核心业务模块的纯净与高内聚性
而且其他的功能模块也可以很好的复用
首先我们要实现两个函数
一个用来前置装饰,一个用来后置装饰:
Function.prototype.before = function(beforeFunc){
var that = this;
return function(){
beforeFunc.apply(this, arguments);
return that.apply(this, arguments);
}
}
Function.prototype.after = function(afterFunc){
var that = this;
return function(){
var ret = that.apply(this, arguments);
afterFunc.apply(this, arguments);
return ret;
}
}
以前置装饰为例
调用before时,先把原函数的引用保存下来
然后返回一个“代理”函数
这样在原函数调用前,先执行扩展功能的函数
而且他们共用同一个参数列表
后置装饰与前置装饰基本类似,只是执行顺序不同
如果不喜欢这种污染原型的方式,也可以这么写:
var before = function(originFunc, beforeFunc){
return function(){
before.apply(this, arguments);
return originFunc.apply(this, arguments);
}
}
var after = function(originFunc, afterFunc){
return function(){
var ret = originFunc.apply(this, arguments);
afterFunc.apply(this, arguments);
return ret;
}
}
使用这种AOP的方式可以完美的对函数进行功能扩展
var foobar = function(x, y, z){
console.log(x, y, z);
}
var foo = function(x, y, z){
console.log(x/10, y/10, z/10);
}
var bar = function(x, y, z){
console.log(x*10, y*10, z*10);
}
foobar = foobar.before(foo).after(bar);
foobar(1, 2, 3);
//0.1 0.2 0.3
//1 2 3
//10 20 30
由于原函数和装饰函数共用一个参数列表
所以我们可以用AOP改变函数参数
var data = {
width: '100px',
height: '100px'
}
var demo = function(data){
console.log(JSON.stringify(data));
}
demo = demo.before(function(data){
data.color = 'red';
});
demo(data);
//{"width":"100px","height":"100px","color":"red"}
最后谈一谈装饰者模式的缺点
它也不是十全十美的
装饰链叠加了函数作用域,如果过长也会产生性能问题
如果原函数上保存了属性,返回新函数后属性会丢失
var demo = function(){
console.log(1);
}
demo.a = 123;
demo = demo.after(function(){
console.log(2);
});
demo();
console.log(demo.a);
//undefined
装饰者模式在开发中非常有用,在框架开发中也十分有用