装饰器模式
装饰器模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
两个特点:
1.为对象添加新功能
2.不改变其原有的结构和功能
装饰器能干嘛:
1.不修改源代码,直接给一个类添加功能
2.给一个类组合很多功能
3.动态地添加或者删除功能
实现装饰器模式的几种方法
使用“类”来实现
例如:孙悟空刚从石头中蹦出来的时候,技能只有sayName和attack,只有等到去了东海之后,拿到了金箍棒,才会增加一个新的技能GoldGun。如果想要在不修改源代码的情况下,给他增加这个技能,就可以使用装饰器模式。
// 一个Monkey类,目的就是为这个Monkey类添加新功能
function Monkey(){
console.log("在一个夜黑风高的晚上,突然从石头中蹦出一只猴子,他是孙悟空")
}
Monkey.prototype = {
sayName:function (){
console.log("我是泼猴孙悟空")
},
attack:function (){
console.log("猴拳出击")
}
}
// 创建一个装饰者,接收一个装饰的对象
var Decorator = function (monkey){
this.monkey = monkey
}
Decorator.prototype = {
sayName:function (){
this.monkey.sayName()
},
attack:function (){
this.monkey.attack()
}
}
// 创建具体的装饰器对象
var GoToDongHai = function (name){ // 接收一个实例对象
Decorator.call(this,name) // Decorator方法.call执行,把this指向改成GoToDongHai的实例对象
}
var monkey = new Monkey() // 生成一个实例对象monkey
GoToDongHai.prototype = new Decorator(monkey) // 将实例对象monkey传入装饰者,new装饰者生成实例对象赋值给装饰器对象的原型
GoToDongHai.prototype.GoldGun = function (){ // 并且在其原型上添加新的属性
console.log("获得金箍棒,吃我一棒")
}
var dongHai = new GoToDongHai(monkey) // 生成实例对象dongHai
dongHai.sayName()
dongHai.attack()
dongHai.GoldGun() // 此时就能在不修改原来代码的情况下拥有新技能
通过递归嵌套
例如:一开始战斗机只有发射普通子弹的功能,需要给它添加发射导弹甚至原子弹的新功能。
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 = new AtomDecorator(new MissileDecorator(plane))
plane.fire(); // 发射普通子弹 发射导弹 发射原子弹
这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。
因为装饰者对象和它所装饰的对象拥有一致的接口,所以它们对使用该对象的客户来说是透明的,被装饰的对象也并不需要了解它曾经被装饰过,这种透明性使得我们可以递归地嵌套任意多个装饰者对象。
修改对象方法
JavaScript 语言动态改变对象相当容易,我们可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式
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();// 分别输出: 发射普通子弹、发射导弹、发射原子弹
装饰函数
在JavaScript 中可以很方便地给某个对象扩展属性和方法,但却很难在不改动某个函数源代码的情况下,给该函数添加一些额外的功能。在代码的运行期间,我们很难切入某个函数的执行环境。
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 f1() {
console.log(1)
}
let f2 = f1.before(function () {
console.log(0)
});
let f3 = f2.after(function () {
console.log(2)
})
f3() // 依次打印 0 1 2
装饰器模式和代理模式的区别
装饰器模式和代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。
代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。
装饰器模式用于一开始不能确定对象的全部功能时。
代理模式通常只有一层代理:本体的引用;
装饰器模式经常会形成一条长长的装饰链。