2 min read

Strategy Pattern

The Strategy pattern allows you to swap out different algorithms or strategies without changing the overall behaviour of the program. This pattern is part of behavioural design pattern.

The Strategy pattern enables following the Open/Closed principle by promoting abstraction, encapsulation and loose coupling between interchangeable behaviours/algorithms and the code that uses them. This means you should be able to add new behaviour or extend a system without needing to modify existing code.

It consists of:

  • A Context class that uses the strategy (but isn't coupled to specific strategies)
  • Different Strategy classes that encapsulate specific algorithms or behaviours
  • The Context gets initialized with a Concrete Strategy and delegates to it
This allows the Context class to remain unchanged while being able to switch strategies easily.

Let's understand from an example:

classDiagram class AutoCharge{ - payment_method + charge(amount) } class PaymentStrategy{ -interface- pay(amount) } AutoCharge ..> PaymentStrategy PaymentStrategy <|.. creditcardpayment paymentstrategy <|.. paypalpayment bitcoinpayment < div

The PaymentStrategy interface defines the common interface for all payment strategies. It has a method pay() that concrete strategies must implement.

The CreditCardPayment, PaypalPayment, and BitcoinPayment classes are concrete strategies that implement the PaymentStrategy interface. They provide specific implementations of the pay() method.

The AutoCharge class is the context. It has a payment_method attribute that stores the strategy to use. The charge() method delegates to the payment_method to handle the payment.

When initializing AutoCharge, you inject a concrete strategy into payment_method. This allows the strategy to be swapped out.

Now AutoCharge can delegate to different payment algorithms interchangeably based on the strategy it is composed with. The Client can swap strategies being used by AutoCharge dynamically at runtime.

# PaymentStrategy interface
class PaymentStrategy
  def pay(amount)
    raise NotImplementedError, "Subclasses must implement this method"
  end
end

# Payment strategies
class CreditCardPayment < PaymentStrategy
  def initialize(card_number, expiry_date)
    @card_number = card_number
    @expiry_date = expiry_date
  end
  
  def pay(amount)
    puts "Charging credit card #{amount}" 
  end
end

class PaypalPayment < PaymentStrategy
  def initialize(email)
    @email = email
  end
  
  def pay(amount)
    puts "Charging PayPal #{amount}"
  end
end

class BitcoinPayment < PaymentStrategy
  def initialize(bitcoin_address)
    @bitcoin_address = bitcoin_address
  end
  
  def pay(amount)
    puts "Charging bitcoin wallet #{amount}" 
  end
end

# Context
class AutoCharge
  attr_accessor :payment_method

  def initialize(payment_method)
    @payment_method = payment_method
  end

  def charge(amount)
    @payment_method.pay(amount)
  end
end


# Usage
credit_card = CreditCardPayment.new("1234-5678-9012-3456", "12/25")
paypal = PayPalPayment.new("example@example.com")
bitcoin = BitcoinPayment.new("1A2b3C4d5E6fG7hI8jK9l")

auto_charge = AutoCharge.new(credit_card)
auto_charge.charge(100)

auto_charge.payment_method = paypal
auto_charge.charge(100) 

auto_charge.payment_method = bitcoin
auto_charge.charge(100)

The key benefits are:

  • Isolates algorithm code from the using context
  • Allows switching strategies easily without changing context code
  • Adheres to open/closed principle (open for extension, closed for modification)

By using the Strategy Pattern, you can easily add new payment methods in the future without modifying the existing code, making your system more flexible and maintainable.