Паттерн Декоратор - динамически наделяет объект новыми возможностями, и является гибкой альтернативой субклассированию в области расширения функциональности.
Данный паттерн относится к структурным паттернам. Для динамического добавления нового поведения используется композиция и делегирование. Компонент может декорироваться любым количеством декораторов.
Ниже рассмотрены примеры реализации данного паттерна. Первый пример показывает применение декораторов в роли дополнительных компонентов для того или иного вида кофе (см.рис. ниже).
/* Component - Beverage */
var Beverage = function () {
this.description = "Unknown Beverage";
this.getDescription = function () {
return this.description;
};
// abstract cost();
this.cost = function () {
throw new Error("Must implement the abstract method - cost()");
};
};
/* Decorator - CondimentDecorator */
var CondimentDecorator = function () {
// abstract getDescription();
this.getDescription = function () {
throw new Error("Must implement the abstract method - getDescription()");
};
};
CondimentDecorator.prototype = new Beverage();
CondimentDecorator.prototype.constructor = CondimentDecorator;
/* ConcreteComponent - Expresso */
var Expresso = function () {
this.description = "Expresso";
this.cost = function () {
return 1.99;
};
};
Expresso.prototype = new Beverage();
Expresso.prototype.constructor = Expresso;
/* ConcreteComponent - HouseBlend */
var HouseBlend = function () {
this.description = "House Blend Coffee";
this.cost = function () {
return 0.89;
};
};
HouseBlend.prototype = new Beverage();
HouseBlend.prototype.constructor = HouseBlend;
/* ConcreteComponent - DarkRoast */
var DarkRoast = function () {
this.description = "Dark Roast Coffee";
this.cost = function () {
return 0.99;
};
};
DarkRoast.prototype = new Beverage();
DarkRoast.prototype.constructor = DarkRoast;
/* ConcreteComponent - Decaf */
var Decaf = function () {
this.description = "Decaf Coffee";
this.cost = function () {
return 1.05;
};
};
Decaf.prototype = new Beverage();
Decaf.prototype.constructor = Decaf;
/* ConcreteDecorator - Mocha */
var Mocha = function (begerage) {
var begerage = begerage;
this.getDescription = function () {
return begerage.getDescription() + ", Mocha";
};
this.cost = function () {
return 0.20 + begerage.cost();
};
};
Mocha.prototype = new CondimentDecorator();
Mocha.prototype.constructor = Mocha;
/* ConcreteDecorator - Milk */
var Milk = function (begerage) {
var begerage = begerage;
this.getDescription = function () {
return begerage.getDescription() + ", Milk";
};
this.cost = function () {
return 0.10 + begerage.cost();
};
};
Milk.prototype = new CondimentDecorator();
Milk.prototype.constructor = Milk;
/* ConcreteDecorator - Soy */
var Soy = function (begerage) {
var begerage = begerage;
this.getDescription = function () {
return begerage.getDescription() + ", Soy";
};
this.cost = function () {
return 0.15 + begerage.cost();
};
};
Soy.prototype = new CondimentDecorator();
Soy.prototype.constructor = Soy;
/* ConcreteDecorator - Whip */
var Whip = function (begerage) {
var begerage = begerage;
this.getDescription = function () {
return begerage.getDescription() + ", Whip";
};
this.cost = function () {
return 0.10 + begerage.cost();
};
};
Whip.prototype = new CondimentDecorator();
Whip.prototype.constructor = Whip;
/* test application - StarbuzzCoffee */
var StarbuzzCoffee = function () {
var beverage = new Expresso();
console.log(beverage.getDescription() + " $" + beverage.cost());
var beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
console.log(beverage2.getDescription() + " $" + beverage2.cost());
var beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
console.log(beverage3.getDescription() + " $" + beverage3.cost());
};
StarbuzzCoffee();
На выходе:
Expresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34
Или вот другой пример на основе списков декораторов:
var Sale = function (price) {
this.price = (price > 0) || 100;
this.decorators_list = [];
};
Sale.decorators = {};
Sale.decorators.fedtax = {
getPrice: function (price) {
return price + price * 5 / 100;
}
};
Sale.decorators.quebec = {
getPrice: function (price) {
return price + price * 7.5 / 100;
}
};
Sale.decorators.money = {
getPrice: function (price) {
return "$" + price.toFixed(2);
}
};
Sale.decorators.cdn = {
getPrice: function (price) {
return "CDN$ " + price.toFixed(2);
}
};
Sale.prototype.decorate = function (decorator) {
this.decorators_list.push(decorator);
};
Sale.prototype.getPrice = function () {
var price = this.price,
i,
max = this.decorators_list.length,
name;
for (i = 0; i < max; i += 1) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}
return price;
};
var Application = function () {
this.run = function () {
var sale = new Sale(100);
sale.decorate('fedtax');
sale.decorate('quebec');
sale.decorate('money');
console.log(sale.getPrice()); // $113
};
};
var application = new Application();
application.run();