Strategy

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Problem

An e-commerce system supports multiple payment methods: credit card, PayPal, and Bitcoin. Embedding payment logic directly into the checkout class forces changes to that class whenever a new payment method is added and violates the open/closed principle.

Real-World Analogy

Think of getting to the airport. You can drive, take a taxi, ride the bus, or call an Uber. The goal is the same (get to the airport), but you pick the strategy based on your budget, time, and luggage. You can switch strategies at the last minute — your trip planning doesn’t change, only the transportation method does.

When You Need It

  • You’re building a payment system that supports credit cards, PayPal, and cryptocurrency — and you want to swap payment methods at runtime without changing the checkout logic
  • You’re implementing a sorting feature where users can sort by name, date, price, or popularity — each sort is a pluggable strategy
  • You’re building a compression tool that supports ZIP, GZIP, and LZ4 — the compression algorithm is interchangeable, and you might add new ones later

UML Class Diagram

classDiagram
    class PaymentContext {
        -IPaymentStrategy strategy
        +SetStrategy(IPaymentStrategy) void
        +Pay(decimal) void
    }
    class IPaymentStrategy {
        <<interface>>
        +Pay(decimal) void
    }
    class CreditCardPayment {
        +Pay(decimal) void
    }
    class PayPalPayment {
        +Pay(decimal) void
    }
    class BitcoinPayment {
        +Pay(decimal) void
    }

    PaymentContext o-- IPaymentStrategy
    IPaymentStrategy <|.. CreditCardPayment
    IPaymentStrategy <|.. PayPalPayment
    IPaymentStrategy <|.. BitcoinPayment

Sequence Diagram

sequenceDiagram
    participant Client
    participant Context
    participant Strategy
    Client->>Context: setStrategy(strategy)
    Client->>Context: execute()
    Context->>Strategy: algorithm()

Participants

Participant Role
PaymentContext Maintains a reference to a strategy and delegates payment to it.
IPaymentStrategy Common interface for all payment algorithms.
CreditCardPayment Processes payment via credit card.
PayPalPayment Processes payment via PayPal.
BitcoinPayment Processes payment via Bitcoin.

How It Works

  1. The client selects a payment strategy and passes it to the PaymentContext.
  2. When Pay() is called, the context delegates to the current strategy.
  3. The strategy can be swapped at runtime without modifying the context.

Applicability

  • Many related classes differ only in their behavior.
  • You need different variants of an algorithm.
  • A class defines many behaviors via conditional statements that can be moved into strategy classes.

Trade-offs

Pros:

  • Algorithms can be swapped at runtime without changing the context
  • Each strategy is isolated in its own class, easy to test independently
  • Avoids conditional statements for selecting behavior

Cons:

  • Clients must be aware of different strategies to select the right one
  • Increases the number of classes — one per algorithm
  • Communication overhead if the strategy interface passes data the algorithm doesn’t need

Example Code

C#

public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount) =>
        Console.WriteLine($"Paid {amount:C} via Credit Card.");
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount) =>
        Console.WriteLine($"Paid {amount:C} via PayPal.");
}

public class BitcoinPayment : IPaymentStrategy
{
    public void Pay(decimal amount) =>
        Console.WriteLine($"Paid {amount:C} via Bitcoin.");
}

public class PaymentContext
{
    private IPaymentStrategy _strategy;

    public void SetStrategy(IPaymentStrategy strategy) => _strategy = strategy;
    public void Pay(decimal amount) => _strategy.Pay(amount);
}

// Usage
var context = new PaymentContext();
context.SetStrategy(new CreditCardPayment());
context.Pay(99.99m);

context.SetStrategy(new PayPalPayment());
context.Pay(49.50m);

context.SetStrategy(new BitcoinPayment());
context.Pay(150.00m);

Delphi

type
  IPaymentStrategy = interface
    procedure Pay(AAmount: Currency);
  end;

  TCreditCardPayment = class(TInterfacedObject, IPaymentStrategy)
  public
    procedure Pay(AAmount: Currency);
  end;

  TPayPalPayment = class(TInterfacedObject, IPaymentStrategy)
  public
    procedure Pay(AAmount: Currency);
  end;

  TBitcoinPayment = class(TInterfacedObject, IPaymentStrategy)
  public
    procedure Pay(AAmount: Currency);
  end;

  TPaymentContext = class
  private
    FStrategy: IPaymentStrategy;
  public
    procedure SetStrategy(AStrategy: IPaymentStrategy);
    procedure Pay(AAmount: Currency);
  end;

procedure TCreditCardPayment.Pay(AAmount: Currency);
begin
  WriteLn(Format('Paid %m via Credit Card.', [AAmount]));
end;

procedure TPayPalPayment.Pay(AAmount: Currency);
begin
  WriteLn(Format('Paid %m via PayPal.', [AAmount]));
end;

procedure TBitcoinPayment.Pay(AAmount: Currency);
begin
  WriteLn(Format('Paid %m via Bitcoin.', [AAmount]));
end;

procedure TPaymentContext.SetStrategy(AStrategy: IPaymentStrategy);
begin
  FStrategy := AStrategy;
end;

procedure TPaymentContext.Pay(AAmount: Currency);
begin
  FStrategy.Pay(AAmount);
end;

// Usage
var
  Context: TPaymentContext;
begin
  Context := TPaymentContext.Create;
  Context.SetStrategy(TCreditCardPayment.Create);
  Context.Pay(99.99);

  Context.SetStrategy(TPayPalPayment.Create);
  Context.Pay(49.50);

  Context.SetStrategy(TBitcoinPayment.Create);
  Context.Pay(150.00);
end;

C++

#include <iostream>
#include <memory>

class IPaymentStrategy {
public:
    virtual ~IPaymentStrategy() = default;
    virtual void Pay(double amount) = 0;
};

class CreditCardPayment : public IPaymentStrategy {
public:
    void Pay(double amount) override {
        std::cout << "Paid $" << amount << " via Credit Card.\n";
    }
};

class PayPalPayment : public IPaymentStrategy {
public:
    void Pay(double amount) override {
        std::cout << "Paid $" << amount << " via PayPal.\n";
    }
};

class BitcoinPayment : public IPaymentStrategy {
public:
    void Pay(double amount) override {
        std::cout << "Paid $" << amount << " via Bitcoin.\n";
    }
};

class PaymentContext {
    std::unique_ptr<IPaymentStrategy> strategy_;
public:
    void SetStrategy(std::unique_ptr<IPaymentStrategy> s) {
        strategy_ = std::move(s);
    }
    void Pay(double amount) { strategy_->Pay(amount); }
};

int main() {
    PaymentContext context;
    context.SetStrategy(std::make_unique<CreditCardPayment>());
    context.Pay(99.99);

    context.SetStrategy(std::make_unique<PayPalPayment>());
    context.Pay(49.50);

    context.SetStrategy(std::make_unique<BitcoinPayment>());
    context.Pay(150.00);
    return 0;
}

Runnable Examples

Language Source
C# Strategy.cs
C++ strategy.cpp
Delphi strategy.pas
  • Flyweight — Strategy objects can be shared as Flyweights if they carry no state.
  • State — State can be seen as an extension of Strategy where transitions between strategies happen automatically.
  • Template Method — Template Method uses inheritance to vary part of an algorithm; Strategy uses delegation to vary the entire algorithm.

Back to top

Design Patterns Guide — content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.