Strategy Pattern — Interchangeable Algorithms at Runtime
What You'll Learn
You will learn how the Strategy Pattern enables selecting an algorithm at runtime by encapsulating each algorithm in its own class, promoting Composition Over Inheritance. You will distinguish Strategy from State and Template Method.
Why It Matters
An e-commerce site calculates shipping costs differently for standard, Express, and international delivery. Embedding all three algorithms in the order class violates the Single Responsibility and Open/Closed Principles. Strategy extracts each shipping method into a separate, swappable component. The result is a system where new shipping methods can be added (FedEx, DHL, drone delivery) without touching existing code — just add a new Strategy class and configure it.
Real-World Use
Sorting Algorithms — you might use quicksort for small arrays, merge sort for stable sorting, and timsort for partially sorted data — all selectable through a common Comparator interface. DodaTech's compression module selects between gzip, brotli, and zstd strategies based on file type and size, allowing users to configure compression levels per content type without changing the compression pipeline code.
The Pattern
Context maintains a reference to a Strategy. Strategy declares the algorithm interface. ConcreteStrategies implement specific algorithms.
from abc import ABC, abstractmethod
class ShippingStrategy(ABC):
@abstractmethod
def calculate(self, weight_kg: float, distance_km: float) -> float:
pass
class StandardShipping(ShippingStrategy):
def calculate(self, weight_kg: float, distance_km: float) -> float:
return weight_kg * 0.5 + distance_km * 0.01
class ExpressShipping(ShippingStrategy):
def calculate(self, weight_kg: float, distance_km: float) -> float:
return weight_kg * 1.0 + distance_km * 0.05 + 10.0
class InternationalShipping(ShippingStrategy):
def calculate(self, weight_kg: float, distance_km: float) -> float:
return weight_kg * 2.0 + distance_km * 0.1 + 15.0
class ShippingCalculator:
def __init__(self, strategy: ShippingStrategy):
self._strategy = strategy
def set_strategy(self, strategy: ShippingStrategy):
self._strategy = strategy
def calculate_cost(self, weight_kg: float, distance_km: float) -> float:
return self._strategy.calculate(weight_kg, distance_km)
calculator = ShippingCalculator(StandardShipping())
weight, distance = 5.0, 200.0
for name, strategy in [("Standard", StandardShipping()), ("Express", ExpressShipping()), ("International", InternationalShipping())]:
calculator.set_strategy(strategy)
cost = calculator.calculate_cost(weight, distance)
print(f"{name}: ${cost:.2f}")
Standard: $4.50
Express: $20.00
International: $45.00
Structure
classDiagram
class Context {
-strategy: Strategy
+setStrategy(Strategy)
+executeStrategy()
}
class Strategy {
<>
+algorithm()
}
class ConcreteStrategyA {
+algorithm()
}
class ConcreteStrategyB {
+algorithm()
}
class ConcreteStrategyC {
+algorithm()
}
Context --> Strategy : delegates
ConcreteStrategyA ..|> Strategy
ConcreteStrategyB ..|> Strategy
ConcreteStrategyC ..|> Strategy
Real-World Usage
- Java
Comparatorinterface — sorting strategies passed toCollections.sort(). - Python
sorted()key function — a function-based Strategy for custom sort ordering. - HTTP authentication mechanisms — Basic, Bearer, Digest, OAuth — all interchangeable through a common
Authenticatorinterface. - Compression algorithms in
tar—--gzip,--bzip2,--xzflags select different compression strategies.
Related Patterns
- State shares the same structure but transitions itself; Strategy is set by the client.
- Template Method defines an algorithm skeleton and lets subclasses override steps; Strategy lets you swap entire algorithms.
- Decorator adds behaviour; Strategy replaces behaviour.
- Flyweight can make Strategy instances reusable across contexts.
Pros and Cons
| Pros | Cons |
|---|---|
| Algorithms are isolated and independently testable | Clients must be aware of different strategies |
| Easy to add new strategies without changing context | Increases number of classes |
| Eliminates conditional statements for algorithm selection | Strategy creation and configuration overhead |
| Composition Over Inheritance — more flexible | Not all algorithms fit the same interface cleanly |
The key insight in the code is that ShippingCalculator holds a reference to a ShippingStrategy and delegates the calculate call. The calculator doesn't know which concrete Strategy it's using — it only knows the interface. This makes the strategies completely interchangeable at runtime, as demonstrated by the loop that swaps strategies and recalculates the same shipment.
Practice Questions
- Implement a payment processing system with CreditCard, PayPal, and Crypto strategies.
- How would you allow the client to configure a Strategy with parameters (e.g., API keys for payment gateways)?
- Compare Strategy with lambdas/first-class functions — when is a full class necessary versus a simple callback?
- Implement a Strategy selector that chooses the best Strategy based on input data characteristics (e.g., selecting compression Strategy based on file type).
Challenge
Implement a Strategy that decorates another Strategy with logging or timing. For example, a TimedShipping Strategy that wraps any shipping Strategy and logs the calculation time.
Real-World Task
Identify a method in your codebase that contains a large if-else chain selecting between algorithms or behaviours. Refactor it using the Strategy Pattern and use DodaTech's A/B testing framework to compare the performance of different strategies in production.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro