一. 装饰者模式的定义
装饰者模式的定义:动态地给一个对象添加额外的职责,而不会影响从这个类派生的其他对象。
二. 装饰者模式的实现
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态添加职责。
在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列表来存储装饰者对象,等到调用方法时,才去遍历该装饰者对象列表中的装饰方法。这样的实现更简单,并且也不涉及继承。
三. 装饰者模式与代理模式的异同点
装饰者模式和代理模式很相像,都描述了怎样为对象提供一定程度上的间接引用,他们的实现部分都保留了对另一个对象的引用,并且向那个对象发送请求。
代理模式和装饰者模式的最重要的区别在于他们的意图和设计目的:
代理模式的目的是,当直接访问本体不方便时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对他的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用是为对象动态添加职责。
代理模式强调的是(代理与本体对象之间的关系),并且这种关系一开始就被可以确定。而装饰者模式用于一开始不能确定对象的全部功能时。
代理模式通常只有一层代理-本体的引用。而装饰者模式经常会形成一条长长的装饰链。
四. 总结
装饰者模式在实际开发中很有用,特别是在框架开发中。我们希望框架提供的是一些稳定而方便移植的功能,那些个性化的功能可以在框架之外动态装饰上去。