- Published on
Exploring the Decorator Pattern in JavaScript
- Authors
- Name
- Omama Zainab
- @omamazainab_
Decorator is a structural design pattern, structural design patterns gather objects and classes together in a larger structure while keeping it well organized and flexible.
Decorator design pattern allows you to modify the object dynamically at runtime by adding new functionalities without affecting existing functionalities. It is done by wrapping the original object with the wrapper objects(decorators) having the required functionalities.
Explanation Using An Example
Let's take an example, we have to build a notification feature to notify people about anything the application asks for. The app should have different notification types like E-mail notifications, push notifications for desktop, mobile, web and SMS notification.
Problem
We might first think to make a base class notifier and make EmailNotifier
, WebPushNotifier
, MobilePushNotifier
, DesktopPushNotifier
and SMSNotifier
it's sub-classes, seems simple. But client app might need more than one notifiers, this could be done by inheritence but there are some issues, many languages do not support multiple inheritence and our base class will be stuffed with sub-classes, code will be repeated a lot and it will be a mess.
Solution
We can solve these issues using association. In which an object has a reference to another object and delegates it some work. Let's see how to use this approach in implementing decorator design pattern.
A wrapper is an object that is linked to the main object, it has same set of methods as the main object. so from client's perspective both are identical. So we have a basic notifier and decorators which will enable us to have more kinds of notifications wrapped around basic notifier. Now we won't have to create tons of sub-classes, just a decorator with same sets of methods.
The client code will now just need to wrap the decorator it needs around the basic notifier object, the resulting object would be structured as a stack, wrapped one on another. The client will be working with the last decorator, it does not have to care about which object is it working with.
Structure
Implementation
Note: *I won't be covering implementation of sending notification instead I will just use console logs to show notification messages to keep the code shorter and focused on decorator design pattern. *
We have made a basic Notifier
class having a send
method and 5 decorator classes EmailDecorator
, SMSDecorator
, WebPushDecorator
, MobilePushDecorator
and DesktopPushDecorator
, implementing send
method and having a wrappe
referencing the object that would be wrapped by that decorator. The send
method in decorator will peroform it's task and then call wrappe
's send
method.
// Notifier class defination
function Notifier() { }
//send message method for basic notifier
Notifier.prototype.send = function(message) {
console.log(`Basic Notifier : ${message}`);
}
// Email notification decorator
function EmailDecorator(wrappe) {
// wrappe will be the obbject that will be decorated by this decorator
this.wrappe = wrappe;
}
// send method becasue the decorators must have similar functionalities so it seems identical to the client
EmailDecorator.prototype.send = function(message) {
console.log("email : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
function SMSDecorator(wrappe) {
this.wrappe = wrappe;
}
SMSDecorator.prototype.send = function(message) {
console.log("sms : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
function WebPushDecorator(wrappe) {
this.wrappe = wrappe;
}
WebPushDecorator.prototype.send = function(message) {
console.log("web push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
function MobilePushDecorator(wrappe) {
this.wrappe = wrappe;
}
MobilePushDecorator.prototype.send = function(message) {
console.log("mobile push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
function DesktopPushDecorator(wrappe) {
this.wrappe = wrappe;
}
DesktopPushDecorator.prototype.send = function(message) {
console.log("desktop push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
function run() {
// config for notifications
let config = {
smsEnabled: true,
emailEnabled: true,
webPushEnabled: true,
mobilePushEnabled: true,
desktopPushEnabled: true,
}
// basic notifier objects
let notifications = new Notifier();
if (config.smsEnabled)
//wrapping basic notifier in decorator
notifications = new EmailDecorator(notifications);
if (config.emailEnabled)
notifications = new SMSDecorator(notifications);
if (config.webPushEnabled)
notifications = new WebPushDecorator(notifications);
if (config.mobilePushEnabled)
notifications = new MobilePushDecorator(notifications);
if (config.desktopPushEnabled)
notifications = new DesktopPushDecorator(notifications);
notifications.send("This is trial notification");
}
run();
A friend of mine suggested that I should use classes too so here is the implementation using classes, both outputs will be the same.
// Notifier class defination
class Notifier{
//send message method for basic notifier
send(message){
console.log(`Basic Notifier : ${message}`);
}
}
// Email notification decorator
class EmailDecorator {
// wrappe will be the obbject that will be decorated by this decorator
constructor(wrappe){
this.wrappe = wrappe;
}
// send method becasue the decorators must have similar functionalities so it seems identical to the client
send(message){
console.log("email : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
}
// SMS notification decorator
class SMSDecorator {
constructor(wrappe){
this.wrappe = wrappe;
}
send(message){
console.log("sms : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
}
// Web notifications decorator
class WebPushDecorator {
constructor(wrappe){
this.wrappe = wrappe;
}
send(message){
console.log("web push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
}
// Mobile notifications decorator
class MobilePushDecorator{
constructor(wrappe){
this.wrappe = wrappe;
}
send(message){
console.log("mobile push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
}
// Desktop notifications decorator
class DesktopPushDecorator{
constructor(wrappe){
this.wrappe = wrappe;
}
send(message){
console.log("desktop push notification : " + message);
// call the wrappe's send method
this.wrappe.send(message);
}
}
function run(){
// config for notifications
let config = {
smsEnabled : true,
emailEnabled : true,
webPushEnabled : true,
mobilePushEnabled : true,
desktopPushEnabled : true,
}
// basic notifier objects
let notifications = new Notifier();
if(config.smsEnabled)
//wrapping basic notifier in decorator
notifications = new EmailDecorator(notifications);
if(config.emailEnabled)
notifications = new SMSDecorator(notifications);
if(config.webPushEnabled)
notifications = new WebPushDecorator(notifications);
if(config.mobilePushEnabled)
notifications = new MobilePushDecorator(notifications);
if(config.desktopPushEnabled)
notifications = new DesktopPushDecorator(notifications);
// client just have to interact with the last decorator
notifications.send("This is trial notification");
}
run();
Output
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1628849716322/5vZk-Ja8V.png align="left")
When To Use Decorator Design Pattern
When you need to add and remove extra behaviors to object at runtime.
When inheritance will make the code too complex and awkward.
Advantages
Object's behavior can be extended without any complexity and without changing other functionality's code.
Any functionality can be added or removed at runtime.
You don't have to build a monolith class having all functionalities in it making the class complex and difficult for extensibility.
Disadvantages
Removing a wrapper from stack is hard.
The initial configuration code might look ugly.
That's a wrap guys. I hope you got what decorator pattern is, how and when to implement it.