Javascript Decorator Pattern
When developing a large application, our main goal is not to repeat ourself and reuse our code. For code reuse we can do many things but one of them is inheritance. Example of inheritance using javascript is:
function Sandwich(name) {
this.name = name;
}
Sandwich.prototype.eat = function () {
console.log("I'm eating " + this.name);
}
function Subway () {
Sandwich.call(this, 'subway');
}
Subway.prototype = Object.create(Sandwich.prototype);
Subway.prototype.cost = function () {
return 5;
}
This is a simple implementation but many of us want different things with our sandwiches. Some like pickles, ketchup while other like mustard and Guacamole and some of us want all of them. And we need to get cost of the sandwich after adding our favorites.
But when we do this using inheritance we would need multiple data types on Sandwich. We would need withPickle, withKetchup, withMustard, withpickleAndMustard, withpickleAndKetch and many others when adding new condiments. These all have same functionality but a tiny difference between them. Now we need to think our implementation and Decorators come in mind.
Decorator
it's basically a wrapper which extends the functionality of an object while maintaining object interface.
For example we need different cost for different sizes. Because Decorators are only wrapper we can wrap new object in funciton like this:
Subway.sixInch = function (subwayObj) { //Method on Subway function object
var cost = subwayObj.cost(); //Get current cost of sandwich
subwayObj.cost = function () { //We aren't changing interface but providing more functionality
return cost - 1;
}
}
Subway.footLong = function (subwayObj) {}; //Default is Foot Long
Subway.partySub = function (subwayObj) {
var cost = subwayObj.cost();
subwayObj.cost = function () {
return cost + 3;
}
}
Subway.partySub = function (subwayObj) {
var cost = subwayObj.cost();
subwayObj.cost = function () {
return cost + 3;
}
}
Subway.pickle = function (subwayObj) {
var cost = subwayObj.cost();
subwayObj.cost = function () {
return cost + 0.12;
}
}
Subway.mustard = function (subwayObj) {
var cost = subwayObj.cost();
subwayObj.cost = function () {
return cost + 0.25;
}
}
Subway.ketchup = function (subwayObj) {
var cost = subwayObj.cost();
subwayObj.cost = function () {
return cost + 0.10;
}
}
And now when we want a party sub with pickle and mustard Then we only need to call these decorator methods on our new object.
var mySandwich = new Subway(); // current cost of sandwich 5
Subway.partySub(mySandwich); // add 3 and cost would be 8
Subway.pickle(mySandwich); // add 0.12 and cost would be 8.12
Subway.mustard(mySandwich); // add 0.25 and cost would be 8.37
console.log(mySandwich.cost());//8.37
We did all this without adding any data type to Subway object for all this various combination. We can change this code to use inheritance and prototype.
function Sandwich() {
this._cost = 0;
}
Sandwich.prototype.cost = function () {
return this._cost;
}
function SandwichDecorator(sandwich) {
Sandwich.call(this);
this.sandwich = sandwich;
}
SandwichDecorator.prototype = Object.create(Sandwich.prototype);
SandwichDecorator.prototype.cost = function () {
return this._cost + this.sandwich.cost();
}
function Pickle(sandwich) {
SandwichDecorator.call(this, sandwich);
this._cost = 0.12;
}
Pickle.prototype = Object.create(SandwichDecorator.prototype);
function Subway() {
Sandwich.call(this);
this._cost = 5;
}
Subway.prototype = Object.create(Sandwich.prototype);
var mySandwich = new Subway();
mySandwich = new Pickle(mySandwich);
console.log(mySandwich.cost()); //5.12
In new prototype example we don't need to create cost function again and again. I hope you found this useful.
Sandwich.call(this, ‘subway’);