Decorator Pattern
This lesson discusses the decorator pattern in detail using a coding example.
We'll cover the following
What is the decorator pattern?
The decorator pattern focuses on adding properties, functionalities, and behavior to existing classes dynamically. The additional decoration functionalities aren’t considered essential enough to be a part of the original class definition as they can cause clutter. Hence, the decorator pattern allows modifying the code without changing the original class.
Unlike the creational patterns, the decorator pattern is a structural pattern that does not focus on object creation rather decoration. Hence, it doesn’t rely on prototypal inheritance alone; it takes the object and keeps adding decoration to it. This makes the process more streamlined. Let’s take a look at an example to understand this concept better.
Example
class FrozenYoghurt {constructor(flavor, price) {this.flavor = flavorthis.price = price}orderPlaced() {console.log(`The ${this.flavor} flavor will cost you ${this.price} dollars`);}}// decorator 1function addFlavors(froyo) {froyo.addStrawberry = true;froyo.addVanilla = true;froyo.price += 20;froyo.updatedInfo = function(){console.log(`The updated price after adding flavors is ${froyo.price} dollars`)}return froyo;}// decorator 2function addToppings(froyo) {froyo.hasSprinkles = true;froyo.hasBrownie = true;froyo.hasWafers = true;froyo.allToppings = function(){console.log("Your froyo has sprinkles, brownie, and wafers")}return froyo;}//using decorators//creating a froyoconst froyo = new FrozenYoghurt("chocolate",10)froyo.orderPlaced()//adding flavorsvar froyowithFlavors = addFlavors(froyo)froyowithFlavors.updatedInfo()//adding toppingsvar froyoWithToppings = addToppings(froyo)froyoWithToppings.allToppings()
Explanation #
Let’s start by visualizing the code:
From the diagram above, you can see that we first create a chocolate froyo that costs 10 dollars. Anyone who wants a froyo will perform this step.
After the first step, you have the option to add more flavors as well. Some people might get a single flavor, pay for it, and then leave. However, in our case, we add two more flavors to it, strawberry and vanilla. Each of them costs 10 dollars, so now we will have to pay an extra 20 dollars.
At this point, again, you have the option to either pay and leave, or add extra toppings to the froyo. In our case, we add three toppings: sprinkles, a brownie, and wafers.
So how is the decorator pattern being implemented here? Getting the chocolate froyo was the major step; however, the additional flavors and the toppings are both optional. Hence, they are both additional choices that the person may or may not consider. So both these steps are not included in the first step instead added later on as decoration.
Now that the concept is clear. Let’s look at its implementation using code.
We start by creating a FrozenYoghurt
class:
class FrozenYoghurt {
constructor(flavor, price) {
this.flavor = flavor
this.price = price
}
orderPlaced() {
console.log(`The ${this.flavor} flavor will cost you ${this.price} dollars`);
}
}
It’s constructor
initializes the flavor
and price
properties to the values passed into the constructor
. It also contains the orderPlaced
function, which displays the flavor
you got and its price
.
Now, it’s time for the decoration. You have the option to addFlavors
and addToppings
to your foyo.
Here’s what the addFlavors
function looks like:
function addFlavors(froyo) {
froyo.addStrawberry = true;
froyo.addVanilla = true;
froyo.price += 20;
froyo.updatedInfo = function(){
console.log(`The updated price after adding flavors is ${froyo.price} dollars`)
}
return froyo;
}
It adds the strawberry and vanilla flavors to the froyo
instance and increases the price
by 20
dollars. The updatedInfo
function shows the updated price
.
The addToppings
function is as follows:
function addToppings(froyo) {
froyo.hasSprinkles = true;
froyo.hasBrownie = true;
froyo.hasWafers = true;
froyo.allToppings = function(){
console.log("Your froyo has sprinkles, brownie, and wafers")
}
return froyo;
}
It adds sprinkles, a brownie, and wafers to the froyo
instance. The allToppings
function displays all the toppings added.
When to use the decorator pattern? #
JavaScript developers can use the decorator pattern when they want to easily modify or extend the functionality of an object without changing its base code.
It can be used if an application has a lot of distinct objects with the same underlying code. Instead of creating all of them using different sub-classes, additional functionalities can be added to the objects using the decorator pattern.
A simple example can be of text formatting where you need to apply different formattings such as bold, italics, and underline to the same text.
Now that you know what an abstract pattern is, it’s time to implement it!