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:
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.
|..>