TypeScript Decorator Design Pattern

Ibrahim sengun
3 min readJan 5, 2024

--

What is decorator design pattern?

The decorator is a structural design pattern that provides the ability to attach new responsibilities to objects. It places the objects into a special wrapper, and by using this wrapper, it adds new responsibilities.

There are several terminologies in the decorator design pattern. These are:

  • Component Interface: It defines the common properties for both wrappers and the wrapped object.
  • Concrete Component: These are the objects that are being wrapped.
  • Base Decorator: It connects to the wrapped object via the component interface and delegates the job to wrapped objects.
  • Concrete Decorator: It defines additional behaviors that can be added to components. It can override the behaviors in the base decorator to manipulate the work operation time before or after.
  • Client: This refers to the application or function that communicates with the component interface.

When should the decorator design pattern be used?

The Decorator design pattern can be used when there is a need to add new behaviors to objects at runtime without breaking the code. It can also be used in cases where it is not possible to extend an object’s behavior with inheritance.

How to implement decorator design pattern in TypeScript

Let’s try to apply the Decorator design pattern to TypeScript. First, let’s imagine a scenario where we are dealing with a notification system in an app. Our notification system was originally designed to send a few basic notifications. However, as our project grew, we needed to add new behaviors to our notification system. We wanted it to send notifications through different channels like Facebook, email, etc.

Initially, we considered designing these new abilities using inheritance by extending our base notification class to create separate channels for our new requirements. Although this approach worked fine, we felt uneasy. What if, in the future, we decided to expand our notification channels? We would have to design more subclasses for each channel, potentially increasing the complexity of our code. Even if we were okay with this, there was another problem: if we wanted to send notifications simultaneously through multiple channels or all of them, our current design wouldn’t allow us to make changes or switch strategies at runtime.

To address these issues, we decided to change strategies and employ the Decorator design pattern. With this design pattern, we wrapped our notification logic with a wrapper class that would change the behavior of our notification logic. It would connect to our notification logic via a reference with an interface. Instead of inheritance, we formed a composition and aggregation relationship between our wrapper and notification logic.

With this design, we can now add as many abilities as we want and even alter the execution order of these abilities.

Decorator desing pattern diagram

Decorator desing pattern diagram

Decorator desing pattern code

// Component interface
interface INotification {
send(): void;
}

// Concrete Component
class BasicNotification implements INotification {
send(): void {
console.log("Sending basic notification");
}
}

// Base Decorator
class NotificationDecorator implements INotification {
protected notification: INotification;

constructor(notification: INotification) {
this.notification = notification;
}

send(): void {
this.notification.send();
}
}

// Concrete Decorators
class EmailDecorator extends NotificationDecorator {
send(): void {
super.send();
console.log("Sending notification via Email");
}
}

class SMSDecorator extends NotificationDecorator {
send(): void {
super.send();
console.log("Sending notification via SMS");
}
}

// Client
/*
--- Sending Notifications ---
Sending basic notification
*/
const basicNotification: INotification = new BasicNotification();
console.log("--- Sending Notifications ---");
basicNotification.send();


/*
--- Sending Notifications with Email ---
Sending basic notification
Sending notification via Email
*/
const notificationWithEmail: INotification = new EmailDecorator(basicNotification);
console.log("\n--- Sending Notifications with Email ---");
notificationWithEmail.send();


/*
--- Sending Notifications with SMS ---
Sending basic notification
Sending notification via SMS
*/
const notificationWithSMS: INotification = new SMSDecorator(basicNotification);
console.log("\n--- Sending Notifications with SMS ---");
notificationWithSMS.send();


/*
--- Sending Notifications with Email and SMS ---
Sending basic notification
Sending notification via SMS
Sending notification via Email
*/
const notificationWithBoth: INotification = new EmailDecorator(new SMSDecorator(basicNotification));
console.log("\n--- Sending Notifications with Email and SMS ---");
notificationWithBoth.send();

--

--