Damián Pumar

← Back to blog

Published on January 2, 2024 by Damián Pumar

Sometimes we need to add new functionality to an existing object, but we don’t want to modify the existing structure of the object, we want to add the new functionality in a transparent way for the consumer, still all tests passing and no violate any SOLID principle.

To achieve this we can use the decorator pattern 💪.

What is the decorator pattern? 🤔

The decorator pattern is a design pattern that allows us to add new functionality to an existing object without modifying its structure. The decorator pattern is a structural pattern, part of 23 GOF patterns.

Advantages of the decorator pattern 🤩

Disadvantages of the decorator pattern 😔

How to implement the decorator pattern in Typescript? 🤓

Imagine that we have a class has an existing behavior, but we need to extends this functionality but in other object instead to add new lines on the existing class.

For example imagine that we have a class called NotificationSender that has a method called send that sends a notification to a user via email. Now we need to add a new functionality to this class, for example we need to send two notifications to the user, one by email and another by sms.

We could modify the NotificationSender class and add the new functionality, but this would violate the open-closed principle, which says that a class should be open for extension but closed for modification. For this reason we will use the decorator pattern.

Let’s see the original NotificationSender class 👇

class NotificationSender {
  send(user: User, message: string) {
    // send notification to user by email
  }
}

And now let’s see how we can add the new functionality using the decorator pattern 👇

First step 🏃‍♂️

The first step is to create an interface that defines the behavior of the class that we want to decorate. In our case we will create an interface called INotificationSender that defines the behavior of the NotificationSender.

interface INotificationSender {
  send(user: User, message: string): void;
}

Second step 🏃‍♂️

Extend the NotificationSender class to implement the INotificationSender interface.

class NotificationSender implements INotificationSender {
  send(user: User, message: string) {
    // send notification to user by email
  }
}

Third step 🏃‍♂️

Create a class that implements the INotificationSender interface and receives an instance of the INotificationSender interface in the constructor. In our case we will create a class called NotificationSenderViaSMS that implements the INotificationSender interface.

class NotificationSenderViaSMS implements INotificationSender {
  constructor(private notificationSender: INotificationSender) {}

  send(user: User, message: string) {
    this.notificationSender.send(user, message);
  }
}

This is the key of the decorator pattern, the INotificationSender implementation class receives an instance of the INotificationSender (other object with same type).

Fourth step 🏃‍♂️

Implement the sms notification into NotificationSenderViaSMS class.

First we send the notification by email using the notificationSender instance and then we send the notification by sms.

class NotificationSenderViaSMS implements INotificationSender {
  constructor(private notificationSender: INotificationSender) {}

  send(user: User, message: string) {
    this.notificationSender.send(user, message);

    // Here send notification to user by sms
  }
}

Last step 🏃‍♂️

We need to modify the instantiation of the NotificationSender class to use the NotificationSenderViaSMS class and pass it the NotificationSender instance.

// Previous code
const notificationSender = new NotificationSender();

// New code
const notificationSender = new NotificationSenderViaSMS(
  new NotificationSender()
);

Conclusion 🤓

The decorator pattern is a very useful pattern that allows us to add new functionality to an existing object without modifying its structure, as a consumer just modify the instantiation of the class and the decorator pattern does the rest.

References 📚

Bye 🙌

Written by Damián Pumar

Something wrong? Let me know 🙏

← Back to blog

Recent Blogposts

  • Communication between <iframe>

    Communication between <iframe>

    The iframe's are a powerful tool, but sometimes we need to send data from the iframe to the parent window, in this post I will explain how can you send data from <iframe> to its parent window without any cors restriction and sending complex objects.

  • Frontend dependency injection container

    Frontend dependency injection container

    do you know what dependency injection is? How do dependency injection containers work? In this post I will explain how it works and how you can implement it in your project, we will see the advantages, disadvantages with code examples.

  • Reuse Vue component in a directive ♻️

    Reuse Vue component in a directive ♻️

    In this post I will show you how to reuse a vue component in a directive without duplicate code or create vanilla javascript replicating this behavior. Sometimes it is necessary to do it because you will have to rewrite all the vue code in vanilla javascript, and sometimes that would be a lot of work, right?