Strategy Pattern in Javascript

Strategy Pattern in JavaScript

In this article we will describe the Strategy Pattern. We will define a real world scenario and explain how to solve it using the Strategy Design Pattern approach.

According to Wikipedia: in computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

Real World Scenario and Strategy Pattern Class Diagram

Let’s look at the scenario below:

In this scenario we have a Warehouse Management System, WMS. When order is shipped, meaning, tracking information is entered into the system, the system needs to check order’s source and send the tracking information to the appropriate destination. If order comes from Shopify ecommerce platform, the system needs to send tracking info to Shopify. If it is BigCommerce order, the tracking needs to be sent to BigCommerce. Otherwise, the system needs an email to the customer on the order that their purchase is on the way.

Let’s look at the Strategy Pattern implementation diagram:

At the top of the inheritance tree there’s an abstract class TrackingInfoSender It is extended by children: ShopifyTrackingInfoSender, BigCommerceTrackingInfoSender, and EmailTrackingInfoSender. TrackingInfoSenderClass has an abstract method sendTracking. Since it is an abstract method TrackingInfoSender class also works as an interface, meaning all children have to implement sendTrackingMethod. Sometimes an advantage of having an abstract parent class instead of a simple interface is that duplicate logic of child classes can be pushed up to the parent class enforcing DRY principle. In our case each child class has assign a corresponding order to order property. This logic is handled by the parent.

Another method of the TrackingInfoSender class is getSender(). This is a static factory method. It will decided which child tracking info sender class to instantiate depending on the order source.

Strategy Pattern Implementation

Now let’s translate this diagram into code. If you prefer, you can also watch this video on how to implement the Strategy Pattern.

We will be working in a Node.js project configured with TypeScript, ESLint, and Prettier.

Let’s create order.ts file in strategyPattern folder.

export enum OrderSource {
  Shopify = "shopify",
  BigCommerce = "bigcommerce",
  Internal = "internal",
}

export class Order {
  source: OrderSource;

  constructor(source: OrderSource) {
    this.source = source;
  }
}

This code defines Order class. There’s only one property on this class – source. However, in real life there will be many more properties. For the sake of our discussion we have to keep things as simple as possible.

Next, let’s create trackingInfo.ts file in the same folder and put the following code in it.

import { Order, OrderSource } from "./order.js";

export default abstract class TrackingInfoSender {
  protected order: Order;

  constructor(order: Order) {
    this.order = order;
  }

  abstract sendTracking(): void;

  static getSender(order: Order): TrackingInfoSender {
    let sender = new EmailTrackingInfoSender(order);
    switch (order.source) {
      case OrderSource.Shopify:
        sender = new ShopifyTrackingInfoSender(order);
        break;
      case OrderSource.BigCommerce:
        sender = new BigCommerceTrackingInfoSender(order);
        break;
      default:
        sender = new EmailTrackingInfoSender(order);
    }

    return sender;
  }
}

class ShopifyTrackingInfoSender extends TrackingInfoSender {
  constructor(order: Order) {
    super(order);
  }
  sendTracking(): void {
    console.log("Sending tracking information to Shopify");
  }
}

class BigCommerceTrackingInfoSender extends TrackingInfoSender {
  constructor(order: Order) {
    super(order);
  }
  sendTracking(): void {
    console.log("Sending tracking information to BigCommerce");
  }
}

class EmailTrackingInfoSender extends TrackingInfoSender {
  constructor(order: Order) {
    super(order);
  }
  sendTracking(): void {
    console.log("Emailing tracking information to the customer");
  }
}

This code exports TrackingInfoSender abstract class. The class has a static factory method getSender that decides which concrete tracking info sender class to instantiate depending on order source. Each of tracking info sender strategy classes extend TrackingInfoSender and implement sendTracking method. This way we ensure that sendTracking method can be called on any strategy class. Another advantage of having a parent class is that logic common to all tracking info sender classes can be implemented in TrackingInfoSender parent class, thus making sure the DRY principle is followed.

Finally, let’s create trackingInfo.ts in strategyPattern folder and put everything together.

import { Order, OrderSource } from "./order.js";
import TrackingInfoSender from "./trackingInfo.js";

const orders = [
  new Order(OrderSource.Shopify),
  new Order(OrderSource.Internal),
];

const trackingInfoSenders = orders.map((order) =>
  TrackingInfoSender.getSender(order)
);

function sendTrackingInfo(trackingInfoSender: TrackingInfoSender) {
  trackingInfoSender.sendTracking();
}

export default function runStrategyPattern() {
  trackingInfoSenders.forEach((sender) => sendTrackingInfo(sender));
}

First we create an array of orders – one order is from Shopify and another order is an internal one. We map over the array and get tracking number info sender class for each of the orders. getSender factory method will determine which strategy will be applied when sending tracking data. After that we define function sendTrackingInfo. It takes trackingInfoSender as a parameter. Since tracking info sender classes extend TrackingInfoSender , we can type trackingInfoSender parameter as TrackingInfoSender class. Finally runStrategyPattern function calls forEach method on the array of trackingInfoSenders and calls sendTrackingInfo function on each info sender.

Now we can import runStrategyPattern function in index.ts file in src folder and run it.

Conclusion

To summarize: We first defined each strategy as its own class. Then we created a parent class that has a factory method that decides which strategy to use by instantiating an appropriate class. The parent class also has an abstract method that each child strategy class has to implement. This ensures we can type strategy classes using the parent class and call that method on any of the classes.

In conclusion, the Strategy Pattern proves to be a powerful ally in JavaScript development, providing a structured approach to code organization and enhancing flexibility. By applying this design pattern, you empower your projects with a scalable and maintainable architecture. As you navigate the dynamic landscape of JavaScript, consider the Strategy Pattern as a valuable tool to achieve code excellence. Happy coding!

Share this article

Posted

in

by