JavaScript设计模式十三(装饰者模式)

JavaScript设计模式十三(装饰者模式)

定义:

装饰者模式是指能够在不改变对象自身的前提下,在程序运行期间动态的给对象添加职责。与继承相比,装饰者模式更加方便,是一种“即用即付”的方式。

模拟传统面向对象的装饰者模式

我们先模拟面向对象的装饰者模式,假如我们在编写飞机大战的游戏,随着经验值的增加,我们操作的飞机可以升级,一开始只能发送普通的子弹,升级后能够发射导弹,再升级后发射原子弹。

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();

我们看到MissileDecorator和AtomDecoratord的构造函数接受plane参数,并且保存,它们的fire方法中除了输出还调用了plane对象的fire方法。这种给对象动态添加职责的方法,并没有真正的改动对象自身,而是把一个对象放到另一个对象中,这些对象以一条链的方式调用,形成一个聚合对象,并且这些对象都有同样的接口,对用户来说是透明的,用户不需要关系这个对象是否被装饰过,我们可以递归的嵌套多个装饰者对象。

JavaScript的装饰者模式

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中为函数添加功能最简单的办法就是直接改写函数,但是这也是最粗暴的办法,违反了开放-封闭原则
例如:

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();

这样我们并没有去修改原来的代码,但是也存在一些问题,比如必须维护_a这个变量,this被劫持了,虽然上面的代码体现不出来,我们看看这个代码:

var _getElementById = document.getElementById;

document.getElementById = function(id) {
    alert(1);
    return _getElementById(id)
}

var button = document.getElementById('button');

这段代码执行后会报错,因为_getElementById是一个全局函数,调用后this指向的是window,但是document.getElemntById内部的实现里面this的实现是指向document的,所以这种方式有问题

解决方法其实很简单,利用apply就可以啦

var _getElementById = document.getElementById;

document.getElementById = function(){
    alert(1);
    return _getElementById.apply(document, arguments);
}

var button = docuemnt.getElementById('button');

但是这种方式很麻烦,我们看如何使用AOP来实现

用AOP装饰函数

我们之前第一章讲过AOP的高阶函数应用:

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;
    };
}

我们在看如何利用before来解决上一节的问题

document.getElementById = document.getElementById.before(function(){
    alert(1);
});
var button = document.getElementById('button');

不过这种方式是在Function.prototype上添加的方法,这种其实是污染原型的,我们可以稍微修改一下:

var before = function(fn, beforefn) {
    return function() {
        beforefn.apply(this, arguments);
        return fn.apply(this, arguments);
    }
}

var a = before(function(){
    alert(1);
}, function(){
    alert(2);
});

a = before(a, function(){
    alert(3);
});
a();

装饰者模式和代理模式

我们回想一下发现装饰者模式和代理模式很像,两者都是描述了如何给对象提供一定程度上的引用,他们实现上也都保留了对另外一个对象的引用,并且向那个对象发送请求。
装饰者模式和代理模式最大的区别在于他们的意图和目的。代理模式的目的是当本体不方便访问的时候,给本体提供一个替代者,它强调的是一种关系,并且这种关系y开始就可以被确定了;而装饰者模式用于一开始不能确定对象的全部功能,代理模式通常只有一层代理-本体的引用,而装饰者模式通常会形成一条长长的装饰链。

猜你喜欢

转载自blog.csdn.net/lihangxiaoji/article/details/80179078