Understanding Strategy and Template Patterns: Key Differences and Practical Examples

Understanding Strategy and Template Patterns: Key Differences and Practical Examples

Design patterns are proven solutions to common software design problems that can help make code more modular, reusable, and maintainable. In this article, we explore two important behavioral design patterns: the Strategy Pattern and the Template Pattern. While both patterns allow for flexible and customizable behavior, they are applied in different contexts and offer distinct approaches to solving design challenges.

The Strategy Pattern enables the dynamic selection of algorithms or behaviors at runtime, promoting flexibility and extensibility in our codebase. On the other hand, the Template Pattern defines the structure of an algorithm while allowing subclasses to alter specific steps without changing the overall flow.

In the following sections, we will dive deeper into the characteristics of each pattern, along with practical examples to illustrate their usage, similarities, and differences. Whether we're building algorithms, workflows, or process-driven applications, understanding these patterns can help us write cleaner, more maintainable code.


Strategy Pattern

The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm's behavior at runtime. In JavaScript, it is implemented by defining a family of algorithms (or strategies), encapsulating each one in a separate class or function, and making them interchangeable.

Components of the Strategy Pattern:

  1. Context: Maintains a reference to one of the strategy objects and delegates execution to the strategy.
  2. Strategy Interface: Defines a common interface for all supported strategies.
  3. Concrete Strategies: Implement the algorithm using the common interface.

Here’s how we can implement it in JavaScript:

// Strategy Interface (optional in JavaScript but implied)
class PaymentStrategy {
  pay(amount) {
    throw new Error("Method 'pay' should be implemented");
  }
}

// Concrete Strategies
class CreditCardPayment {
  pay(amount) {
    console.log(`Paid $${amount} using Credit Card.`);
  }
}

class PayPalPayment {
  pay(amount) {
    console.log(`Paid $${amount} using PayPal.`);
  }
}

class BitcoinPayment {
  pay(amount) {
    console.log(`Paid $${amount} using Bitcoin.`);
  }
}

// Context
class PaymentContext {
  constructor(strategy) {
    this.strategy = strategy; // The chosen payment strategy
  }

  setStrategy(strategy) {
    this.strategy = strategy; // Dynamically change the strategy
  }

  processPayment(amount) {
    this.strategy.pay(amount); // Delegate to the chosen strategy
  }
}

// Usage
const paymentContext = new PaymentContext(new CreditCardPayment());
paymentContext.processPayment(100); // Paid $100 using Credit Card.

paymentContext.setStrategy(new PayPalPayment());
paymentContext.processPayment(200); // Paid $200 using PayPal.

paymentContext.setStrategy(new BitcoinPayment());
paymentContext.processPayment(300); // Paid $300 using Bitcoin.        

Live Example: https://codesandbox.io/p/sandbox/objective-sanderson-f66z7j

Key Features:

  1. Encapsulation of Behavior: Each payment method is encapsulated in its own class or function, making it easy to add new strategies.
  2. Flexibility: The strategy can be switched dynamically at runtime using the setStrategy() method.
  3. Open/Closed Principle: New strategies can be added without modifying existing code.


Functional Approach (Without Classes)

We can also implement the Strategy Pattern in JavaScript using plain functions:

// Concrete Strategies
const creditCardPayment = (amount) => console.log(`Paid $${amount} using Credit Card.`);
const payPalPayment = (amount) => console.log(`Paid $${amount} using PayPal.`);
const bitcoinPayment = (amount) => console.log(`Paid $${amount} using Bitcoin.`);

// Context
class PaymentContext {
  constructor(strategy) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  processPayment(amount) {
    this.strategy(amount);
  }
}

// Usage
const paymentContext = new PaymentContext(creditCardPayment);
paymentContext.processPayment(100); // Paid $100 using Credit Card.

paymentContext.setStrategy(payPalPayment);
paymentContext.processPayment(200); // Paid $200 using PayPal.

paymentContext.setStrategy(bitcoinPayment);
paymentContext.processPayment(300); // Paid $300 using Bitcoin.        

Use Cases:

  1. Payment gateways (as shown in the example).
  2. Different sorting algorithms.
  3. Validating input data using different rules.
  4. Compression algorithms.

By using the Strategy Pattern, we keep our code modular, maintainable, and easy to extend.


Template Pattern

The Template Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class (abstract or parent class) but lets subclasses override specific steps of the algorithm without changing its structure. It provides a "template" method in the base class that dictates the steps of the algorithm, some of which can be implemented or modified by subclasses.

Key Features of Template Pattern:

Algorithm Structure:

  • The overall structure or sequence of steps is defined in the base class.
  • Subclasses can override specific steps without modifying the overall structure.

Code Reuse:

  • Common behavior is implemented in the base class, while specific behavior is delegated to the subclasses.

Control Inversion:

  • The base class controls the execution flow, while subclasses provide specific implementations.


Example of Template Pattern:

Let's consider a simple example of creating beverages like coffee and tea.

class Beverage {
  prepareRecipe() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
  }

  boilWater() {
    console.log("Boiling water");
  }

  pourInCup() {
    console.log("Pouring into cup");
  }

  // Steps to be implemented by subclasses
  brew() {
    throw new Error("This method should be overridden!");
  }

  addCondiments() {
    throw new Error("This method should be overridden!");
  }
}

class Coffee extends Beverage {
  brew() {
    console.log("Brewing coffee");
  }

  addCondiments() {
    console.log("Adding sugar and milk");
  }
}

class Tea extends Beverage {
  brew() {
    console.log("Steeping the tea");
  }

  addCondiments() {
    console.log("Adding lemon");
  }
}

// Usage
const coffee = new Coffee();
coffee.prepareRecipe();

const tea = new Tea();
tea.prepareRecipe();        

Output:

Boiling water
Brewing coffee
Pouring into cup
Adding sugar and milk

Boiling water
Steeping the tea
Pouring into cup
Adding lemon        

Strategy Pattern vs. Template Pattern

Similarities:

Behavioral Patterns:

  • Both focus on managing algorithms or behaviors.
  • Both promote code reuse by encapsulating behavior and allowing extensions.

Polymorphism:

  • Both patterns use polymorphism to achieve their goals (e.g., subclasses override methods).

Avoiding Duplicated Code:

  • Both eliminate duplicated code by moving common functionality to a base structure (strategy interface or template base class).


Differences:


When to Use Which Pattern?

Use Strategy Pattern:

  • When we need to choose between multiple interchangeable behaviors or algorithms at runtime.
  • When we need to add new behaviors without modifying existing code.

Use Template Pattern:

  • When we have a fixed algorithm structure but need certain steps to be implemented differently by subclasses.
  • When the overall workflow is fixed, and specific variations are the only expected changes.


#designpatterns #softwareengineering #programming #developer #coding #strategypattern #templatepattern #softwaredevelopment #technology


要查看或添加评论,请登录

Amit Kumar的更多文章

社区洞察

其他会员也浏览了