Design Pattern: Strategy (TS)
Tags: designpatterns, typescript, javascript, beginners
Published: 2023-04-04 22:39:47

The strategy pattern is useful in cases where we have multiple algorithms or strategies that can be interchangeable, and we want to encapsulate them behind a common interface. This allows us to easily switch between different strategies without changing the client code, and also promotes separation of concerns by keeping the algorithms isolated in their own classes.

This pattern can be particularly useful in scenarios where the behavior of an object needs to vary dynamically based on different conditions or inputs. It provides a flexible and extensible way to encapsulate different algorithms or strategies and allows for easy customization and modification without modifying the core logic of the client code.

Let's say you have an application that calculates the shipping cost for a package based on the weight and distance. However, the cost calculation depends on the shipping carrier that the user selects. To implement this behavior, we can use the strategy pattern.

First, we define an interface for the shipping strategy:

```ts
interface ShippingStrategy {
  calculateCost(weight: number, distance: number): number;
}
```

Then, we create concrete classes for each shipping carrier that implements the `ShippingStrategy` interface:

```ts
class FedExStrategy implements ShippingStrategy {
  calculateCost(weight: number, distance: number): number {
    // FedEx shipping cost calculation logic here
  }
}

class UPSStrategy implements ShippingStrategy {
  calculateCost(weight: number, distance: number): number {
    // UPS shipping cost calculation logic here
  }
}

class USPSStrategy implements ShippingStrategy {
  calculateCost(weight: number, distance: number): number {
    // USPS shipping cost calculation logic here
  }
}
```
Next, we create a `ShippingCostCalculator` class that takes a ShippingStrategy object as a parameter:

```ts
class ShippingCostCalculator {
  private shippingStrategy: ShippingStrategy;

  constructor(shippingStrategy: ShippingStrategy) {
    this.shippingStrategy = shippingStrategy;
  }

  calculateCost(weight: number, distance: number): number {
    return this.shippingStrategy.calculateCost(weight, distance);
  }
}
```
Finally, we can use the `ShippingCostCalculator` class to calculate the shipping cost based on the selected carrier:

```ts
const fedExStrategy = new FedExStrategy();
const upsStrategy = new UPSStrategy();
const uspsStrategy = new USPSStrategy();

const calculator = new ShippingCostCalculator(fedExStrategy);
const cost = calculator.calculateCost(10, 100);
console.log(cost); // Output: FedEx shipping cost for a 10 lb package traveling 100 miles

calculator.shippingStrategy = upsStrategy;
const cost = calculator.calculateCost(10, 100);
console.log(cost); // Output: UPS shipping cost for a 10 lb package traveling 100 miles

calculator.shippingStrategy = uspsStrategy;
const cost = calculator.calculateCost(10, 100);
console.log(cost); // Output: USPS shipping cost for a 10 lb package traveling 100 miles
```
In this example, we used the strategy pattern to encapsulate the shipping cost calculation logic for each carrier and to allow the client code to switch between different strategies at runtime.

The strategy pattern is commonly used in situations where different algorithms need to be applied to the same problem or input data, and the choice of algorithm needs to be made at runtime based on specific conditions or configuration settings.