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列表来存储装饰者对象,等到调用方法时,才去遍历该装饰者对象列表中的装饰方法。这样的实现更简单,并且也不涉及继承。

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

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

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

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

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

时间: 2024-08-01 16:39:08

JavaScript中的装饰者模式详解的相关文章

深入理解JavaScript系列(29):设计模式之装饰者模式详解

 这篇文章主要介绍了深入理解JavaScript系列(29):设计模式之装饰者模式详解,装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数),需要的朋友可以参考下     介绍 装饰者提供比继承更有弹性的替代方案. 装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数). 装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的.

JavaScript中的replace()方法使用详解

  这篇文章主要介绍了JavaScript中的replace()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 该方法找到一个正则表达式的字符串之间的匹配,并取代了匹配的子带的新的子串. 替换字符串可以包含以下特殊替换模式: 语法 ? 1 string.replace(regexp/substr, newSubStr/function[, flags]); 下面是参数的详细信息: regexp : 一个RegExp对象.匹配被替换参数的返回#2. substr : 一个字符串,由

JavaScript中的splice()方法使用详解

  这篇文章主要介绍了JavaScript中的splice()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 JavaScript数组的splice()方法改变数组的内容,增加了新的元素,同时消除旧元素. 语法 ? 1 array.splice(index, howMany, [element1][, ..., elementN]); 下面是参数的详细信息: index : 在该索引开始改变的数组. howMany : 整数,表示旧数组元素数去除.如果的howmany为0,没有元

JavaScript中的getDay()方法使用详解

  这篇文章主要介绍了JavaScript中的getDay()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 javascript Date.getDay()方法按照本地时间返回一周中的一天为所述指定的日期.通过getDay返回的值是对应于星期几的整数:0代表星期日,1代表星期一,2表示星期二,依此类推. 语法 ? 1 Date.getDay() 下面是参数的详细信息: NA 返回值: 按照本地时间返回星期几为指定日期. 例子: ? 1 2 3 4 5 6 7 8 9 10 11

JavaScript中reduce()方法的使用详解

  这篇文章主要介绍了JavaScript中reduce()方法的使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 JavaScript 数组reduce()方法同时应用一个函数针对数组的两个值(从左到右),以减至一个值. 语法 ? 1 array.reduce(callback[, initialValue]); 下面是参数的详细信息: callback : 函数执行在数组中每个值 initialValue : 对象作为第一个参数回调的第一次调用使用 返回值: 返回数组的减少单一个值

JavaScript中的bold()方法使用详解

  这篇文章主要介绍了JavaScript中的bold()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 此方法将导致就好像它是在一个标签的字符串显示为粗体. 语法 ? 1 string.bold( ) 下面是参数的详细信息: NA: 返回值: 返回字符串含有标签 例子: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <html> <head> <title>JavaScript String bold() Method</

JavaScript中的small()方法使用详解

  这篇文章主要介绍了JavaScript中的small()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 此方法导致要显示在一个小的字体,就好像它是在一个标记的字符串. 语法 ? 1 string.small( ) 下面是参数的详细信息: NA 返回值: 返回字符串标签 例子: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 <html> <head> <title>JavaScript String small() Method<

JavaScript中的lastIndexOf()方法使用详解

  这篇文章主要介绍了JavaScript中的lastIndexOf()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 此方法调用String对象之内返回索引指定的值最后一次出现,开始搜索在的fromIndex或如果没有找到该值则返回-1. 语法 ? 1 string.lastIndexOf(searchValue[, fromIndex]) 下面是参数的详细信息: searchValue : 一个字符串,表示要搜索的值 fromIndex : 在调用字符串内的位置,从开始搜索.

JavaScript中的slice()方法使用详解

  这篇文章主要介绍了JavaScript中的slice()方法使用详解,是JS入门学习中的基础知识,需要的朋友可以参考下 此方法提取字符串的一部分,并返回一个新的字符串. 语法 ? 1 string.slice( beginslice [, endSlice] ); 下面是参数的详细信息: beginSlice : 从零开始的索引位置开始提取 endSlice : 从零开始的索引位置结束提取.如果省略,切片中提取的字符串的末尾 注意:作为一个负指数,endSlice表示从字符串末尾的偏移. s