剑客
关注科技互联网

设计模式笔记(五):JavaScript中的装饰者模式

一. 装饰者模式的定义

装饰者模式的定义:动态地给一个对象添加额外的职责,而不会影响从这个类派生的其他对象。

二. 装饰者模式的实现

装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态添加职责。

在JavaScript的实际实现中,可以将本体对象放到装饰者对象中,这些对象以一条链的方式进行引用,形成一个聚合对象。本体对象和装饰者对象都拥有相同的接口,当请求到达链中的装饰者对象,这个装饰者对象会执行自身,随后将请求转发给本体对象。

2.1 通过对象覆写来实现装饰者模式

代码如下:

/* =============== 本体对象 =============== */
var Person = function() {};
Person.prototype.work = function() {
    console.log("工作");
};

/* =============== 装饰者对象 =============== */
/*
 * 装饰者对象:程序员
 */
var ProgramerDecorator = function(person) {
    this.person = person;
};
ProgramerDecorator.prototype.work = function() {
    this.person.work(); // 执行本体对象的方法

    // 执行装饰者对象自身的方法
    console.log("写程序");
};

/*
 * 装饰者对象:厨师
 */
var ChiefDecorator = function(person) {
    this.person = person;
};
ChiefDecorator.prototype.work = function() {
    this.person.work(); // 执行本体对象的方法

    // 执行装饰者对象自身的方法
    console.log("炒菜");
};

/* =============== 客户端调用 =============== */
var person = new Person();
person = new ProgramerDecorator(person);

person.work(); // 分别输出:工作、写程序

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

/* =============== 客户端调用 =============== */
var person = new Person();
person = new ProgramerDecorator(person);
person = new ChiefDecorator(person);

person.work(); // 分别输出:工作、写程序、炒菜

装饰者模式将本体对象嵌入到装饰者对象中,实际相当于本体对象被装饰者对象包装起来,形成一条包装链。请求随着这条链依次传递到所有对象,每个对象都有处理这个请求的机会。

2.2 改良版:使用构造函数的静态属性实现装饰者模式

上面的装饰者模式通过重写对象来实现。但是这样会有一个很大的问题:会导致原型链的丢失。使得最开始的Person类中的其他方法和属性可能丢失。

下面我们使用构造函数的静态属性来实现装饰者模式,使原型链得以延续。代码如下:

/* =============== 本体对象 =============== */
var Person = function() {};
Person.prototype.work = function() {
    console.log("工作");
};

// 装饰者对象的调用继承实现
Person.prototype.decorate = function(decorator) {
    var F = function() {},
        overrides = this.constructor.decorators[decorator],
        i,
        newObj;
    F.prototype = this;
    newObj = new F();
    newObj.super = F.prototype;

    for(i in overrides) {
        if(overrides.hasOwnProperty(i)) {
            newObj[i] = overrides[i];
        }
    }
    return newObj;
};

/* =============== 装饰者对象 =============== */
Person.decorators = {};

/*
 * 装饰者对象:程序员
 */
Person.decorators.programer = {
    work: function() {
        this.super.work();
        console.log("写程序");
    }
};

/*
 * 装饰者对象:厨师
 */
Person.decorators.chief = {
    work: function() {
        this.super.work();
        console.log("炒菜");
    }
};

/* =============== 客户端调用 =============== */
var person = new Person();
person = person.decorate("programer");
person = person.decorate("chief");

person.work(); // 分别输出:工作、写程序、炒菜

这样,新装饰的对象newObj将继承目前所有用的对象(无论是原始对象,还是已经添加了最后的装饰者的对象),以便于子对象可以访问父对象。

2.3 进化版:使用列表实现装饰者模式

通过将装饰者对象包装到列表中,可以考虑不需要使用继承,利用JavaScript的动态性质,来实现装饰者模式。代码如下:

/* =============== 本体对象 =============== */
var Person = function() {
    this.decorators_list = []; // 装饰者对象列表
};
Person.prototype.work = function() {
    console.log("工作"); // 本体对象方法

    this.renderDecorator("work"); // 渲染装饰者对象方法
};

// 装饰者对象列表动态添加
Person.prototype.decorate = function(decorator) {
    this.decorators_list.push(decorator);
};

// 遍历装饰者对象列表,并渲染装饰者对象的对应方法
Person.prototype.renderDecorator = function(method) {
    var decoratorsList = this.decorators_list,
        decorators = this.constructor.decorators,
        len = decoratorsList.length;

    for(var i = 0; i < len; i++) {
        (function(i) {
            var name = decoratorsList[i],
            decorator = decorators[name];
            if(typeof decorator[method] == "function") {
                decorator[method]();
            }
        })(i);
    }
};

/* =============== 装饰者对象 =============== */
Person.decorators = {};

/*
 * 装饰者对象:程序员
 */
Person.decorators.programer = {
    work: function() {
        console.log("写程序");
    }
};

/*
 * 装饰者对象:厨师
 */
Person.decorators.chief = {
    work: function() {
        console.log("炒菜");
    }
};

/* =============== 客户端调用 =============== */
var person = new Person();
person.decorate("programer");
person.decorate("chief");

person.work(); // 分别输出:工作、写程序、炒菜

通过decorators_list列表来存储装饰者对象,等到调用方法时,才去遍历该装饰者对象列表中的装饰方法。这样的实现更简单,并且也不涉及继承。

三. 装饰者模式与代理模式的异同点

装饰者模式和代理模式很相像,都描述了怎样为对象提供一定程度上的间接引用,他们的实现部分都保留了对另一个对象的引用,并且向那个对象发送请求。

代理模式和装饰者模式的最重要的区别在于他们的意图和设计目的:

  • 代理模式的目的是,当直接访问本体不方便时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对他的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用是为对象动态添加职责。
  • 代理模式强调的是(代理与本体对象之间的关系),并且这种关系一开始就被可以确定。而装饰者模式用于一开始不能确定对象的全部功能时。
  • 代理模式通常只有一层代理-本体的引用。而装饰者模式经常会形成一条长长的装饰链。

四. 总结

装饰者模式在实际开发中很有用,特别是在框架开发中。我们希望框架提供的是一些稳定而方便移植的功能,那些个性化的功能可以在框架之外动态装饰上去。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址