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
- The client selects a payment strategy and passes it to the
PaymentContext. - When
Pay()is called, the context delegates to the current strategy. - 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 |
Related Patterns
- 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.