Javascript. Decorator pattern

Паттерн Декоратор - динамически наделяет объект новыми возможностями, и является гибкой альтернативой субклассированию в области расширения функциональности.
Данный паттерн относится к структурным паттернам. Для динамического добавления нового поведения используется композиция и делегирование. Компонент может декорироваться любым количеством декораторов.






Ниже рассмотрены примеры реализации данного паттерна. Первый пример показывает применение декораторов в роли дополнительных компонентов для того или иного вида кофе (см.рис. ниже).



/* 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();