When building applications, one of the most common challenges is managing behaviors that can vary over time.
Behavioral Design Patterns helps us design how objects interact with each other in an effective way
One of the most useful behavioral patterns is the Strategy Pattern
Strategy pattern defines a family of algorithms encapsulate each one and make them interchangeable
it provides a way to change the behavior of a class without extending it
Instead of hardcoding behavior inside a class, the behavior is delegated to a separate object called a strategy.
The Problem
Imagine you're building an e-commerce application.
Customers can pay using:
- PayPal
- Credit Card
- Bank Transfer
An initial implementation might look like this:
class Checkout
def pay(method, amount)
case method
when :paypal
puts "Processing PayPal payment of $#{amount}"
when :credit_card
puts "Processing Credit Card payment of $#{amount}"
when :bank_transfer
puts "Processing Bank Transfer payment of $#{amount}"
end
end
end
# Usage
checkout = Checkout.new
checkout.pay(:paypal, 100)
Problems
Every time you add a new payment method:
- You modify
Checkout - The class becomes larger
- Testing becomes harder
- You violate the Open/Closed Principle (open for extension, closed for modification)
Applying the Strategy Pattern
Instead of putting all payment logic inside Checkout, we'll create separate strategy classes.
Step 1: Define the Strategies
class PaymentStrategy
# or make as module and use include instead of <
def pay(amount)
raise NotImplementedError
end
end
class PayPalStrategy < PaymentStrategy
def pay(amount)
puts "Processing PayPal payment of $#{amount}"
end
end
class VisaStrategy < PaymentStrategy
def pay(amount)
puts "Processing Visa payment of $#{amount}"
end
end
class BankTransferStrategy < PaymentStrategy
def pay(amount)
puts "Processing Bank Transfer payment of $#{amount}"
end
end
Step 2: Create the Context
The Context is the class that uses a strategy.
class Checkout
def initialize(payment_strategy)
@payment_strategy = payment_strategy
end
def pay(amount)
@payment_strategy.pay(amount)
end
end
Step 3: Use Different Strategies
checkout = Checkout.new(PayPalStrategy.new)
checkout.pay(100)
checkout = Checkout.new(VisaStrategy.new)
checkout.pay(100)
Runtime Strategy Switching
You can even change strategies while the application is running.
checkout = Checkout.new(PayPalStrategy.new)
checkout.pay(100)
checkout.payment_strategy = BankTransferStrategy.new
checkout.pay(200)
When Should You Use the Strategy Pattern?
The Strategy Pattern is a good choice when:
- You have multiple implementations of the same behavior.
- You find yourself writing large case or if/else statements.
- You want to follow the Open/Closed Principle.
- You need to switch behavior dynamically at runtime.
- You want to isolate and test algorithms independently.
In Ruby, where objects are lightweight and composition is encouraged, the Strategy Pattern often feels natural and can significantly improve the design of your applications.
for a full example in ruby check this https://github.com/ShroukAbozeid/design-pattern/tree/main/strategy
Top comments (0)