Strategy Design Pattern in Typescript

Strategy Design Pattern in Typescript

Strategy Design Pattern defines a family of algorithms,
encapsulates each one, and enables selecting an algorithm from the pool at runtime by using composition over inheritance. Algorithms are interchangeable, meaning that they are replaceable for each other.

In this article, I will elaborate on when and how to use strategy design pattern with a practical example.

WHEN TO USE STRATEGY DESIGN PATTERN?

When more than one child class are using the same algorithm from a pool of algorithms and algorithms are not present in the parent class. Then use strategy design pattern.

Here child class A and child class C both execute the same algorithm which is not present in the parent class.

HOW TO IMPLEMENT STRATEGY DESIGN PATTERN?

Let’s understand this using an example of implementing a notification service, where notification vendors are telegram, whatsapp and email. Notification vendors can send single and bulk notifications.

The UML for the following will be:

To apply the strategy pattern we are going to have:

  • Concrete strategy classes implementing strategy interface.

  • Context class has a strategy (interface).

  • Context class having child classes.

Below is the typescript code and you can play around with it in Typescript Playground

interface INotificationStrategy {
    sendNotification(): void;
}

interface IStrategyConstructor {
    // A Constructor for the INotificationStrategy
    new (vendorName: string): INotificationStrategy
}

class NotificationVendor {

    private strategy: INotificationStrategy;

    constructor(strategy: INotificationStrategy){
        this.strategy = strategy;
    }

    public sendNotification(){
        this.strategy.sendNotification();
    }

}

class Email extends NotificationVendor {

    constructor(strategy: IStrategyConstructor){
        super(new strategy('Email'));
    }
}

class Telegram extends NotificationVendor {
    constructor(strategy: IStrategyConstructor){
        super(new strategy('Telegram'));
    }
}

class Whatsapp extends NotificationVendor {
    constructor(strategy: IStrategyConstructor){
        super(new strategy('Whatsapp'));
    }
}

class SingleNotification implements INotificationStrategy {

    private vendorName: string;

    constructor(vendorName: string){
        this.vendorName = vendorName;
    }

    public sendNotification(): void {
        console.log(`Sent single notification through ${this.vendorName}`);
    }
}

class BulkNotification implements INotificationStrategy {
    private vendorName: string;

    constructor(vendorName: string){
        this.vendorName = vendorName;
    }

    public sendNotification(): void {
        console.log(`Sent bulk notifications through ${this.vendorName}`);
    }
}

const vendor1: NotificationVendor = new Email(SingleNotification);
vendor1.sendNotification();

const vendor2: NotificationVendor = new Telegram(BulkNotification);
vendor2.sendNotification();

const vendor3: NotificationVendor = new Whatsapp(BulkNotification);
vendor3.sendNotification();

Output of above code:

In our code we have implemented all the concrete strategy classes which are SingleNotification and BulkNotification. We have a context class which is NotificationVendor which has child classes Telegram, Whatsapp and Email. Using constructor injection we are defining which strategy has to be executed by the class.

CONCLUSION

The benefits of using strategy design pattern are :

  • It follows single responsibility principle.

  • It follows open closed principle. For e.g., if I want to add a new strategy to send notifications only to specific contacts that can be easily done by creating a new class which implements INotificationStrategy

I hope you understood strategy design pattern.

To contact me I have attached my Linkedin, feel free to ask any questions or provide constructive criticism.