Design Patterns: Strategy Pattern

·

3 min read

The Strategy pattern is a behavioral design pattern that allows objects to be used interchangeably by defining a family of algorithms and encapsulating them in separate classes. This pattern is suitable for projects where there is a need to dynamically change the behavior of an object at runtime, based on different contexts or conditions.

One example of a project where the Strategy pattern is useful is in the development of a search engine that supports multiple search algorithms. Different search algorithms may be more effective for different types of queries or data sets. By using the Strategy pattern, the search engine can be designed to dynamically select the appropriate search algorithm based on the query and the data being searched. This can help to improve the efficiency and accuracy of the search results and make it easier to add new search algorithms in the future.

Another example of a project where the Strategy pattern is useful is in the development of a game that has multiple difficulty levels. Different difficulty levels may require different strategies for enemy AI or player abilities. By using the Strategy pattern, the game can be designed to dynamically select the appropriate strategy based on the difficulty level. This can help to create a more challenging and engaging gameplay experience for the player.

In general, the Strategy pattern should be used in projects where there is a need to dynamically change the behavior of an object at runtime, based on different contexts or conditions. It can help to improve the flexibility and maintainability of the code, by reducing the coupling between the object and its algorithms and making it easier to add or modify algorithms over time.

Let's take a look at a simple but practical application of the Strategy pattern.

// Payment Strategy
class PaymentStrategy {
  constructor(paymentMethod) {
    this.paymentMethod = paymentMethod;
  }

  pay(amount) {
    throw new Error('pay method must be implemented');
  }
}

// Payment Strategies
class CreditCardStrategy extends PaymentStrategy {
  constructor() {
    super('Credit Card');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement credit card payment logic here...
  }
}

class PayPalStrategy extends PaymentStrategy {
  constructor() {
    super('PayPal');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement PayPal payment logic here...
  }
}

class ApplePayStrategy extends PaymentStrategy {
  constructor() {
    super('Apple Pay');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement Apple Pay payment logic here...
  }
}

// Payment Processor
class PaymentProcessor {
  constructor(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }

  processPayment(amount) {
    this.paymentStrategy.pay(amount);
  }
}

// Usage
const paymentMethod = 'Credit Card';
const amount = 100;

let paymentStrategy;

switch (paymentMethod) {
  case 'Credit Card':
    paymentStrategy = new CreditCardStrategy();
    break;
  case 'PayPal':
    paymentStrategy = new PayPalStrategy();
    break;
  case 'Apple Pay':
    paymentStrategy = new ApplePayStrategy();
    break;
  default:
    throw new Error('Invalid payment method');
}

const paymentProcessor = new PaymentProcessor(paymentStrategy);
paymentProcessor.processPayment(amount);
// Output: Paying 100 with Credit Card...

In the above code, we define a PaymentStrategy class that provides a pay method to process the payment based on the selected payment method. We also define three payment strategy classes: CreditCardStrategy, PayPalStrategy, and ApplePayStrategy, which implements the same pay method to process the payment based on the selected payment method.

We then define a PaymentProcessor class that takes a PaymentStrategy instance in its constructor, and provides a processPayment method to trigger the payment processing using the selected payment strategy.

In the usage example, we create a new instance of the appropriate payment strategy class based on the paymentMethod parameter, and pass it to a new instance of the PaymentProcessor class. We can then call the processPayment method on the PaymentProcessor instance to process the payment using the selected payment strategy.

The Strategy pattern provides a flexible and decoupled architecture for the payment processing system, where the payment processing logic is encapsulated in the PaymentStrategy classes and the client code can dynamically select the appropriate payment strategy without tightly coupling to the payment processing logic. New payment strategy classes can be added dynamically by creating new classes that implement the pay method and passing them to a new instance of the PaymentProcessor class.

Did you find this article valuable?

Support Clint by becoming a sponsor. Any amount is appreciated!