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