Strategy Pattern

The Strategy Pattern is a Behavioral Design Pattern that enables selecting an algorithm’s behavior at runtime. The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This approach allows clients to choose which algorithm to use without altering the object’s structure. By isolating the algorithm from the context in which it’s used, the Strategy Pattern provides flexibility and promotes clean, maintainable code.

This pattern is especially useful when an object needs to perform similar actions based on different conditions or data but without tightly coupling the algorithms to the object’s core logic. Common use cases include sorting, filtering, and dynamic behavior selection in applications.

Key Characteristics of Strategy Pattern

  1. Encapsulation of Algorithms:
    • Each algorithm is encapsulated in its own strategy class, which allows for easy switching between strategies and cleaner code, as algorithms are separated from the main logic.
  2. Interchangeability of Strategies:
    • The Strategy Pattern enables dynamic switching of strategies at runtime, allowing the client to choose the most appropriate strategy based on the context.
  3. Adherence to Open-Closed Principle:
    • By encapsulating each algorithm in a separate class, the Strategy Pattern makes it easy to add new strategies without modifying existing code, adhering to the Open-Closed Principle.
  4. Decouples Behavior from Context:
    • The pattern separates the algorithm’s implementation from the object that uses it. This decoupling makes it easy to modify or extend the behavior independently of the context.
  5. Promotes Reusability:
    • Strategies can be reused across different contexts, allowing for consistency and better reusability of algorithms throughout the application.
  6. Easy to Maintain and Extend:
    • By isolating algorithms into their own classes, the Strategy Pattern reduces the complexity of the code, making it easier to maintain, test, and extend.

Strategy Pattern in Node.js

In Node.js, the Strategy Pattern is useful in applications that require flexible algorithms or operations that may vary based on user input or specific conditions. For example, let’s create a payment processing system that can dynamically switch between different payment methods (e.g., PayPal, credit card, and bank transfer) based on user selection.

Example Scenario: Payment Processing System

In this example, we’ll create a PaymentProcessor class that delegates payment processing to different strategies. Each strategy will represent a different payment method and will implement a standard interface.

Step 1: Define the Strategy Interface

The PaymentStrategy interface defines a pay method that each concrete strategy must implement.

// Strategy Interface
class PaymentStrategy {
  pay(amount) {
    throw new Error("Method 'pay()' must be implemented.");
  }
}

Step 2: Create Concrete Strategies

Each payment method (PayPal, credit card, and bank transfer) is encapsulated in its own class that implements the PaymentStrategy interface.

// Concrete Strategy: PayPal
class PayPalStrategy extends PaymentStrategy {
  constructor(email) {
    super();
    this.email = email;
  }

  pay(amount) {
    console.log(`Processing PayPal payment of $${amount} for ${this.email}`);
  }
}

// Concrete Strategy: Credit Card
class CreditCardStrategy extends PaymentStrategy {
  constructor(cardNumber) {
    super();
    this.cardNumber = cardNumber;
  }

  pay(amount) {
    console.log(`Processing credit card payment of $${amount} with card ${this.cardNumber}`);
  }
}

// Concrete Strategy: Bank Transfer
class BankTransferStrategy extends PaymentStrategy {
  constructor(accountNumber) {
    super();
    this.accountNumber = accountNumber;
  }

  pay(amount) {
    console.log(`Processing bank transfer of $${amount} from account ${this.accountNumber}`);
  }
}

Step 3: Define the Context (PaymentProcessor)

The PaymentProcessor class serves as the context, which maintains a reference to a PaymentStrategy and delegates the payment processing to the selected strategy.

// Context: PaymentProcessor
class PaymentProcessor {
  constructor() {
    this.paymentStrategy = null;
  }

  // Set the payment strategy
  setPaymentStrategy(strategy) {
    this.paymentStrategy = strategy;
  }

  // Process the payment
  pay(amount) {
    if (!this.paymentStrategy) {
      console.log("Payment strategy not set.");
      return;
    }
    this.paymentStrategy.pay(amount);
  }
}

Step 4: Using the Strategy Pattern

Now, let’s use the PaymentProcessor to handle different payment methods based on the strategy set.

// Creating a PaymentProcessor instance
const paymentProcessor = new PaymentProcessor();

// Choosing PayPal strategy
const payPalStrategy = new PayPalStrategy("user@example.com");
paymentProcessor.setPaymentStrategy(payPalStrategy);
paymentProcessor.pay(100); // Output: Processing PayPal payment of $100 for user@example.com

// Choosing Credit Card strategy
const creditCardStrategy = new CreditCardStrategy("4111111111111111");
paymentProcessor.setPaymentStrategy(creditCardStrategy);
paymentProcessor.pay(250); // Output: Processing credit card payment of $250 with card 4111111111111111

// Choosing Bank Transfer strategy
const bankTransferStrategy = new BankTransferStrategy("1234567890");
paymentProcessor.setPaymentStrategy(bankTransferStrategy);
paymentProcessor.pay(500); // Output: Processing bank transfer of $500 from account 1234567890

Output:

Processing PayPal payment of $100 for user@example.com
Processing credit card payment of $250 with card 4111111111111111
Processing bank transfer of $500 from account 1234567890

Explanation:

  • The PaymentProcessor context sets a PaymentStrategy and delegates the payment processing to the selected strategy.
  • This approach allows the client to switch between payment methods dynamically without modifying the PaymentProcessor or payment strategies.

Real-World Examples of Strategy Pattern

  1. Sorting Algorithms in Data Processing:
    • In applications that process data, the Strategy Pattern can be used to switch between different sorting algorithms (e.g., quicksort, mergesort, bubblesort) based on data size or specific requirements. For example, if the data is mostly sorted, an insertion sort might be used; otherwise, quicksort is chosen.
  2. Compression and Encoding:
    • The Strategy Pattern is useful in applications that compress or encode files. Different compression algorithms (e.g., ZIP, GZIP, LZMA) can be represented as strategies, allowing the client to choose the appropriate compression method based on file type or size.
  3. Payment Gateways in E-Commerce:
    • E-commerce platforms often support multiple payment gateways (e.g., PayPal, Stripe, credit cards). The Strategy Pattern allows the application to dynamically select the desired payment gateway based on user preference or availability.
  4. Validation Logic in Forms:
    • In applications with complex form validation, the Strategy Pattern can be used to encapsulate different validation strategies (e.g., email, phone number, password strength). Each input field can apply a specific validation strategy without modifying the form’s core logic.
  5. Logging Strategies:
    • Applications that require flexible logging can use the Strategy Pattern to choose different logging mechanisms (e.g., file logging, console logging, or cloud logging). The application can dynamically switch between logging strategies based on environment or configuration.
  6. Authentication Mechanisms:
    • In applications that support multiple authentication methods (e.g., OAuth, JWT, basic authentication), the Strategy Pattern enables dynamic selection of the authentication strategy based on user requirements or system configuration.
  7. Discount Calculation in Retail Systems:
    • Retail or e-commerce applications can use the Strategy Pattern to apply different discount calculation strategies. For example, they may offer percentage-based discounts, fixed discounts, or seasonal discounts, each encapsulated as a separate strategy.

Conclusion

The Strategy Pattern is a valuable design pattern for managing algorithms or behaviors that need to be interchangeable within a specific context. By encapsulating each strategy in its own class, the pattern provides flexibility, reusability, and a clean approach to selecting and executing algorithms at runtime. This pattern is especially useful in applications that require dynamic behavior selection based on user input, configuration, or environmental factors.

In Node.js, the Strategy Pattern is frequently used in applications that require multiple implementations for a specific action, such as payment processing, data sorting, or authentication. By using the Strategy Pattern, developers can create modular, maintainable, and flexible code that allows the application to adapt dynamically to changing conditions or requirements. Whether managing payment methods, sorting data, or logging events, the Strategy Pattern offers a structured approach to implementing and extending algorithms with minimal impact on the overall system.

Leave a comment

I’m Tran Minh

Hi, I’m Trần Minh, a Solution Architect passionate about crafting innovative and efficient solutions that make technology work seamlessly for you. Whether you’re here to explore the latest in tech or just to get inspired, I hope you find something that sparks joy and curiosity. Let’s embark on this exciting journey together!

Let’s connect