DEV Community

Cover image for Design Patterns in Ruby #1: Strategy Pattern
Shrouk Abozeid
Shrouk Abozeid

Posted on

Design Patterns in Ruby #1: Strategy Pattern

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 3: Use Different Strategies

checkout = Checkout.new(PayPalStrategy.new)
checkout.pay(100)
checkout = Checkout.new(VisaStrategy.new)
checkout.pay(100)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)