Skip to main content

Introduction To The Strategy Design Pattern

The Strategy pattern is a behavioral design pattern that lets you define a family of algorithms, put each of them into separate classes, and make their objects interchangeable. In other words, it's a way to change the behavior of an object at runtime without changing its implementation.

In this diagram, you can see the classes and their methods. Arrows are used to show relationships between classes. For example, the PaymentStrategy interface is realized (implemented) by PayPalStrategy, CreditCardStrategy, and BitcoinStrategy classes, and this is denoted by the arrows that point from these classes to PaymentStrategy. The ShoppingCart class uses PaymentStrategy, which is shown by the arrow from ShoppingCart to PaymentStrategy.

The components of the Strategy pattern include:

  1. Strategy: This is an interface common to all versions (strategies or behaviors) of some algorithm. The context uses this interface to call the algorithm defined by the concrete strategies.

  2. Concrete Strategies: These implement different versions of an algorithm the context uses.

  3. Context: The context defines and maintains a reference to a strategy object, and can define an interface to let the strategy access its data.

Classic Implementation

Assume we have a payment system where a user can choose to pay via PayPal, Credit Card, or Bitcoin. We can use the Strategy pattern to change the payment algorithm at runtime based on the user's choice.

First, we define the Strategy interface:

interface PaymentStrategy {
pay(amount: number): void;
}

Then we implement the Concrete Strategies:

class PayPalStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid ${amount} using PayPal.`);
}
}

class CreditCardStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid ${amount} using Credit Card.`);
}
}

class BitcoinStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid ${amount} using Bitcoin.`);
}
}

Next, we implement the Context:

class ShoppingCart {
private amount: number;
private strategy: PaymentStrategy;

constructor(strategy: PaymentStrategy) {
this.amount = 0;
this.strategy = strategy;
}

setPaymentStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}

addToCart(value: number): void {
this.amount += value;
}

checkout(): void {
this.strategy.pay(this.amount);
this.amount = 0;
}
}

Now, we can use these classes like this:

let cart = new ShoppingCart(new PayPalStrategy());
cart.addToCart(100);
cart.addToCart(200);
cart.checkout(); // Output: Paid 300 using PayPal.

cart.setPaymentStrategy(new CreditCardStrategy());
cart.addToCart(100);
cart.checkout(); // Output: Paid 100 using Credit Card.

cart.setPaymentStrategy(new BitcoinStrategy());
cart.addToCart(500);
cart.checkout(); // Output: Paid 500 using Bitcoin.

As you can see, the Strategy pattern allows you to switch algorithms (payment methods in this case) on the fly, providing flexibility and making it easier to add new algorithms without modifying existing code. This is in line with the Open/Closed principle of SOLID, which states that software entities should be open for extension but closed for modification.

When To Use Strategy Pattern

The Strategy pattern isn't something you'd typically apply to a system from the very start of development. More often, you'd start using it when the context class gets bloated with massive conditionals that switch the class's behavior depending on the values of the fields, parameters, or environment.

The key indicators that the Strategy pattern may be beneficial include:

Multiple Conditional Statements

When you find yourself using multiple conditional statements to choose different variations of an algorithm, you should consider the Strategy pattern. These conditionals could exist in a single method, or be spread across multiple methods and classes.

Preparation for the Future

If you anticipate your algorithms will need to change or expand in the future, you might want to use the Strategy pattern to keep your code flexible and easy to update. Even if there are only two algorithms now, the Strategy pattern may be worthwhile if the software is expected to evolve.

Complex External Libraries or Frameworks

Sometimes you might use a complex library or framework for just a single specific task. By using the Strategy pattern, you can isolate all the work into a single class, making it easier to replace, mock or manage.

The Need for Switching Algorithms at Runtime

If your application needs to switch its algorithms while it's running based on the conditions or user actions, you may need the Strategy pattern. The pattern allows for efficient swapping of the algorithms used within a program at runtime.

Remember, design patterns are solutions to recurring problems; they're not hard rules, but templates to be used when they fit. There are situations where the Strategy pattern might not be the best solution, so it's important to consider other design patterns as well before deciding on the best one for a particular situation.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle