还是拿自行车车店的例子,当自行车车店针对不同品牌不同车型的车可以自由定制一些东西时怎么办? 比如,顾客想给某种型号的车加一个照面灯。再比如,它们想给车刷个拉风的漆。 如果使用继承,那子类就成千上万了。 这个时候,就可以用到Decorator模式了:
var Bicycle = new Interface("Bicycle", ["assemble", "wash", "ride", "repair", "getPrice"]);
/* Acme ComfortCruiser class. */
var AcmeComfortCruiser = function () { };
AcmeComfortCruiser.prototype = {
assemble: function () {
console.log("Acme ComfortCruiser Bicycle assembled.");
},
wash: function () {
console.log("Acme ComfortCruiser Bicycle washed.");
},
ride: function () {
console.log("Acme ComfortCruiser Bicycle ride.");
},
repair: function () {
console.log("Acme ComfortCruiser Bicycle repaired.");
},
getPrice: function () {
return 399.00;
}
};
AcmeComfortCruiser.prototype.constructor = AcmeComfortCruiser;
/* bicycle decorator super class */
var BicycleDecorator = function (bicycle) {
Interface.ensureImplements(bicycle, [Bicycle]);
this.component = bicycle;
};
BicycleDecorator.prototype = {
assemble: function () {
return this.component.assemble();
},
wash: function () {
return this.component.wash();
},
ride: function () {
return this.component.ride();
},
repair: function () {
return this.component.repair();
},
getPrice: function () {
return this.component.getPrice();
}
};
BicycleDecorator.prototype.constructor = BicycleDecorator;
/* Headlight decorator */
var HeadlightDecorator = function (bicycle) {
HeadlightDecorator.superClass.constructor.call(this, bicycle);
};
extend(HeadlightDecorator, BicycleDecorator);
HeadlightDecorator.prototype.assemble = function () {
this.component.assemble();
console.log("Attach headlight to handlebars.");
};
HeadlightDecorator.prototype.getPrice = function () {
return this.component.getPrice() + 15.00;
};
/* test */
(function () {
var bicycle = new AcmeComfortCruiser();
// note: bicycle直接指向被decorate后的对象
bicycle = new HeadlightDecorator(bicycle);
bicycle.assemble();
bicycle.getPrice();
})();
上述代码,关键就在于BicycleDecorator类,它是Decorator的典型实现。同时,为了通用便利,我们将他作为基类,HeadlightDecorator就是它的一个子类。在测试部分,演示了怎么来使用Decorator子类。 注意的是:被装饰的对象的指针最终指向于被装饰后的结果。
装饰过程中,需要重写方法时,可以根据需要先调用被装饰对象的方法,或者后调用,甚至可以完全不用。下面演示的装饰者的assemble方法,先装饰再调用原对象的方法:
var FrameColorDecorator = function(bicycle, frameColor) {
FrameColorDecorator.superClass.constructor.call(this, bicycle);
this.frameColor = frameColor;
}
extend(FrameColorDecorator, BicycleDecorator);
FrameColorDecorator.prototype.assemble = function() {
console.log('Paint the frame ' + this.frameColor + ' and allow it to dry. ');
this.component.assemble();
};
FrameColorDecorator.prototype.getPrice = function() {
return this.component.getPrice() + 30.00;
};
/* test */
(function(){
var myBicycle = new AcmeComfortCruiser();
myBicycle = new FrameColorDecorator(myBicycle, 'red');
myBicycle = new HeadlightDecorator(myBicycle);
myBicycle = new HeadlightDecorator(myBicycle);
//myBicycle = new TaillightDecorator(myBicycle);
myBicycle.assemble();
})();
实际上,基于js的灵活性,你甚至可以给装饰者增加新的方法:
var BellDecorator = function(bicycle) {
BellDecorator.superClass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.assemble = function() {
this.component.assemble().
consol.log('Attach bell to handlebars.');
};
BellDecorator.prototype.getPrice = function() {
return this.component.getPrice() + 6.00;
};
BellDecorator.prototype.ringBell = function() {
console.log('Bell rung.');
};
/* test */
(function(){
var myBicycle = new AcmeComfortCruiser();
myBicycle = new HeadlightDecorator(myBicycle);
myBicycle = new BellDecorator(myBicycle);
myBicycle.ringBell();
})();
够帅吧? 但是,上述代码如果是先上灯泡再加铃呢? 这个时候ringBell方法就用不了!
对BicycleDecorator改进如下:
Interface.ensureImplements(bicycle, [Bicycle]);
this.component = bicycle;
for (var key in this.component) {
// only remain function member
if (typeof (this.component[key]) != "function") {
continue;
}
if (typeof (this[key]) != "undefined") {
continue;
}
//this[key] = this.component[key];
var that = this;
(function (methodName) {
that[methodName] = function (paras) {
that.component[methodName](paras);
};
})(key);
}
};
BicycleDecorator.prototype = {
assemble: function () {
return this.component.assemble();
},
wash: function () {
return this.component.wash();
},
ride: function () {
return this.component.ride();
},
repair: function () {
return this.component.repair();
},
getPrice: function () {
return this.component.getPrice();
}
};
BicycleDecorator.prototype.constructor = BicycleDecorator;
它的核心思想就是:当你装饰时,它会检查装饰者是否有被装饰者没有的方法。如果有,它会对应给被装饰者也加上这么一个方法,并且调用装饰者的方法。这样就可以让装饰链中新增的方法传承下去了。
结合Decorator pattern和Code Complete中所提到的table什么方法来着的,对于整个自行车店的代码改进如下:
var Bicycle = new Interface("Bicycle", ["assemble", "wash", "ride", "repair", "getPrice"]);
/* Acme Speedster class. */
var AcmeSpeedster = function () { }
AcmeSpeedster.prototype = {
assemble: function () {
console.log("Acme Speedster Bicycle assembled.");
},
wash: function () {
console.log("Acme Speedster Bicycle washed.");
},
ride: function () {
console.log("Acme Speedster Bicycle ride.");
},
repair: function () {
console.log("Acme Speedster Bicycle repaired.");
},
getPrice: function () {
return 199.00;
}
};
AcmeSpeedster.prototype.constructor = AcmeSpeedster;
/* Acme Lowrider class. */
var AcmeLowrider = function () { }
AcmeLowrider.prototype = {
assemble: function () {
console.log("Acme Lowrider Bicycle assembled.");
},
wash: function () {
console.log("Acme Lowrider Bicycle washed.");
},
ride: function () {
console.log("Acme Lowrider Bicycle ride.");
},
repair: function () {
console.log("Acme Lowrider Bicycle repaired.");
},
getPrice: function () {
return 299.00;
}
};
AcmeLowrider.prototype.constructor = AcmeLowrider;
/* Acme ComfortCruiser class. */
var AcmeComfortCruiser = function () { };
AcmeComfortCruiser.prototype = {
assemble: function () {
console.log("Acme ComfortCruiser Bicycle assembled.");
},
wash: function () {
console.log("Acme ComfortCruiser Bicycle washed.");
},
ride: function () {
console.log("Acme ComfortCruiser Bicycle ride.");
},
repair: function () {
console.log("Acme ComfortCruiser Bicycle repaired.");
},
getPrice: function () {
return 399.00;
}
};
AcmeComfortCruiser.prototype.constructor = AcmeComfortCruiser;
/* bicycle decorator super class */
var BicycleDecorator = function (bicycle) {
Interface.ensureImplements(bicycle, [Bicycle]);
this.component = bicycle;
for (var key in this.component) {
// only remain function member
if (typeof (this.component[key]) != "function") {
continue;
}
if (typeof (this[key]) != "undefined") {
continue;
}
//this[key] = this.component[key];
var that = this;
(function (methodName) {
that[methodName] = function (paras) {
that.component[methodName](paras);
};
})(key);
}
};
BicycleDecorator.prototype = {
assemble: function () {
return this.component.assemble();
},
wash: function () {
return this.component.wash();
},
ride: function () {
return this.component.ride();
},
repair: function () {
return this.component.repair();
},
getPrice: function () {
return this.component.getPrice();
}
};
BicycleDecorator.prototype.constructor = BicycleDecorator;
/* Headlight decorator */
var HeadlightDecorator = function (bicycle) {
HeadlightDecorator.superClass.constructor.call(this, bicycle);
};
extend(HeadlightDecorator, BicycleDecorator);
HeadlightDecorator.prototype.assemble = function () {
this.component.assemble();
console.log("Attach headlight to handlebars.");
};
HeadlightDecorator.prototype.getPrice = function () {
return this.component.getPrice() + 15.00;
};
/* FrameColorDecorator class. */
var FrameColorDecorator = function (bicycle, frameColor) {
FrameColorDecorator.superClass.constructor.call(this, bicycle);
this.frameColor = frameColor;
}
extend(FrameColorDecorator, BicycleDecorator);
FrameColorDecorator.prototype.assemble = function () {
console.log('Paint the frame ' + this.frameColor + ' and allow it to dry. ');
this.component.assemble();
};
FrameColorDecorator.prototype.getPrice = function () {
return this.component.getPrice() + 30.00;
};
/* BellDecorator class. */
var BellDecorator = function (bicycle) {
BellDecorator.superClass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.assemble = function () {
this.component.assemble();
console.log('Attach bell to handlebars.');
};
BellDecorator.prototype.getPrice = function () {
return this.component.getPrice() + 6.00;
};
BellDecorator.prototype.ringBell = function () {
console.log('Bell rung.');
};
/* BicycleShop class. */
var BicycleShop = function () { };
BicycleShop.prototype = {
sellBicycle: function (model) {
var bicycle = this.createBicycle.apply(this, arguments);
/*
if(arguments.length > 1){
bicycle = this.createBicycle.apply(this, arguments);
}else{
bicycle = this.createBicycle(model);
}
*/
bicycle.assemble();
bicycle.wash();
return bicycle;
},
createBicycle: function (model) {
throw new Error("BicycleShop is abstract, not implements createBicycle.");
}
};
BicycleShop.prototype.constructor = BicycleShop;
/* Acme Bicycle Shop */
var AcmeBicycleShop = function () { };
extend(AcmeBicycleShop, BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function (model, options) {
var bicycle = new AcmeBicycleShop.models[model]();
Interface.ensureImplements(bicycle, [Bicycle]);
for (var i = 0; i < options.length; i++) {
var decorator = AcmeBicycleShop.options[options[i].name];
if (typeof (decorator) !== "function") {
throw new Error('Decorator ' + options[i].name + ' not found.');
}
var args = options[i].args;
bicycle = new decorator(bicycle, args);
Interface.ensureImplements(bicycle, [Bicycle]);
}
return bicycle;
};
// Model name to class name mapping.
AcmeBicycleShop.models = {
'The Speedster': AcmeSpeedster,
'The Lowrider': AcmeLowrider,
'The Comfort Cruiser': AcmeComfortCruiser
};
// Option name to decorator class name mapping.
AcmeBicycleShop.options = {
'headlight': HeadlightDecorator,
'bell': BellDecorator,
'color': FrameColorDecorator
};
/* test */
(function () {
var alecsCruisers = new AcmeBicycleShop();
var myBicycle = alecsCruisers.sellBicycle('The Speedster', [
{ name: 'color', arg: 'blue' },
{ name: 'headlight' },
{ name: 'bell' }
]);
console.log(myBicycle.getPrice());
})();
可以看到,通过提出配置,车店的造车流程更加简洁了,特别是当车型、装饰选项较多时。
这里没完。实际上,在js中普通方法也可以利用Decorator Pattern。如下:
return function () {
return func.apply(this, arguments).toUpperCase();
};
};
function getDate() {
return (new Date()).toString();
}
function sayMessage(message) {
return message;
}
(function () {
var getUpperCaseDate = upperCaseDecorator(getDate);
console.log(getUpperCaseDate());
var sayUpperCaseMessage = upperCaseDecorator(sayMessage);
console.log(sayUpperCaseMessage("Hello, I am here."));
})();
注意,上述方法装饰者可以支持几乎全部返回字符串的普通方法。
源码download