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 ๐คฉ
- Add new functionality without modifying the existing structure.
- Add new functionality at runtime.
- Add new functionality to an existing object in a transparent way for the consumer.
Disadvantages of the decorator pattern ๐
- It can generate a lot of classes if we use it in an uncontrolled way.
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