Deep Dive into Behavioral Patterns - The Strategy Pattern.

Hey software designers! Today, we’re diving into the Strategy pattern. This pattern is essential for defining a family of algorithms, encapsulating each one, and making them interchangeable. Let’s explore its workings, benefits, and real-world applications with detailed examples.

What is the Strategy Pattern?

The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern allows the algorithm to vary independently from clients that use it.

Real-World Scenario

Imagine you’re developing a payment processing system that needs to support multiple payment methods like credit card, PayPal, and Bitcoin. Each payment method has a distinct processing algorithm.

The Problem

When multiple algorithms need to be used interchangeably, directly implementing them within the context class can lead to a tightly coupled and inflexible codebase. This approach makes it difficult to add or modify algorithms without changing the existing code.

Without Strategy Pattern

class PaymentProcessor
  def process(payment_method, amount)
    if payment_method == 'credit_card'
      puts "Processing credit card payment of #{amount}"
    elsif payment_method == 'paypal'
      puts "Processing PayPal payment of #{amount}"
    elsif payment_method == 'bitcoin'
      puts "Processing Bitcoin payment of #{amount}"
    end
  end
end

processor = PaymentProcessor.new
processor.process('credit_card', 100)
processor.process('paypal', 200)
processor.process('bitcoin', 300)

Drawbacks: The code is tightly coupled to specific algorithms and hard to extend to support new algorithms.

The Solution: Strategy Pattern

Using the Strategy pattern, we can encapsulate each algorithm within a separate strategy class, promoting flexibility and scalability.

With Strategy Pattern

Step 1: Define the Strategy Interface

class PaymentStrategy
  def pay(amount)
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

Step 2: Create Concrete Strategies

class CreditCardStrategy < PaymentStrategy
  def pay(amount)
    puts "Processing credit card payment of #{amount}"
  end
end

class PayPalStrategy < PaymentStrategy
  def pay(amount)
    puts "Processing PayPal payment of #{amount}"
  end
end

class BitcoinStrategy < PaymentStrategy
  def pay(amount)
    puts "Processing Bitcoin payment of #{amount}"
  end
end

Step 3: Implement the Context

class PaymentProcessor
  attr_accessor :strategy

  def initialize(strategy)
    @strategy = strategy
  end

  def process(amount)
    @strategy.pay(amount)
  end
end

processor = PaymentProcessor.new(CreditCardStrategy.new)
processor.process(100)

processor.strategy = PayPalStrategy.new
processor.process(200)

processor.strategy = BitcoinStrategy.new
processor.process(300)

Benefits: Encapsulates each algorithm within a separate strategy class, promoting flexibility and scalability.

Real-World Benefits

Scenario: Adding New Payment Methods

Imagine you need to add a new payment method (e.g., Apple Pay). Using the Strategy pattern, you can easily introduce a new strategy without modifying the existing code.

Without Strategy Pattern:

class PaymentProcessor
  def process(payment_method, amount)
    if payment_method == 'credit_card'
      puts "Processing credit card payment of #{amount}"
    elsif payment_method == 'paypal'
      puts "Processing PayPal payment of #{amount}"
    elsif payment_method == 'bitcoin'
      puts "Processing Bitcoin payment of #{amount}"
    elsif payment_method == 'apple_pay'
      puts "Processing Apple Pay payment of #{amount}"
    end
  end
end

processor = PaymentProcessor.new
processor.process('apple_pay', 400)

Drawbacks: Tightly coupled code that is difficult to maintain and extend.

With Strategy Pattern:

class ApplePayStrategy < PaymentStrategy
  def pay(amount)
    puts "Processing Apple Pay payment of #{amount}"
  end
end

processor = PaymentProcessor.new(ApplePayStrategy.new)
processor.process(400)

Benefits: Clean, maintainable code with high flexibility and extensibility.

Conclusion

The Strategy pattern is a powerful tool for defining a family of algorithms, encapsulating each one, and making them interchangeable. It promotes flexibility, scalability, and maintainability in your code. By using the Strategy pattern, you can easily manage multiple algorithms within your applications without tightly coupling the code. Incorporate the Strategy pattern into your design strategies to build more robust and adaptable software systems.

Stay tuned for more insights into software design principles and patterns.

Thôi Lo Code Đi Kẻo Sếp nạt!!