10-设计模式-装饰器模式(设计模式学习笔记)

267 阅读4分钟

装饰器模式

装饰器模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

两个特点:

1.为对象添加新功能

2.不改变其原有的结构和功能

装饰器能干嘛:

1.不修改源代码,直接给一个类添加功能

2.给一个类组合很多功能

3.动态地添加或者删除功能

实现装饰器模式的几种方法

使用“类”来实现

例如:孙悟空刚从石头中蹦出来的时候,技能只有sayNameattack,只有等到去了东海之后,拿到了金箍棒,才会增加一个新的技能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方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。

image.png

因为装饰者对象和它所装饰的对象拥有一致的接口,所以它们对使用该对象的客户来说是透明的,被装饰的对象也并不需要了解它曾经被装饰过,这种透明性使得我们可以递归地嵌套任意多个装饰者对象。

修改对象方法

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 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。

装饰器模式用于一开始不能确定对象的全部功能时。

代理模式通常只有一层代理:本体的引用;

装饰器模式经常会形成一条长长的装饰链。