The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it. You can select an algorithm at compile time through templates, or at runtime through virtual functions or function pointers. Reach for it when one class keeps growing a switch over algorithms.
The problem
You have a class that performs an operation in multiple ways. The naive approach hardcodes all variants using conditional logic:
class DataProcessor {
public:
void process(Data& data, const std::string& algorithm) {
if (algorithm == "quick_sort") {
// quick sort implementation
} else if (algorithm == "merge_sort") {
// merge sort implementation
} else if (algorithm == "heap_sort") {
// heap sort implementation
}
}
};
This approach has several problems:
- Tight coupling. The processor knows about every algorithm.
- Hard to extend. Adding an algorithm means modifying the processor class.
- Single Responsibility. One class handles multiple concerns.
- Open/Closed Principle. The class is open for extension only through modification.
- Maintainability. The class grows more complex with every algorithm.
The Strategy pattern fixes this by extracting algorithms into separate, interchangeable objects.
Real-world analogy
Think of getting to the airport:
- Goal: get to the airport (the operation)
- Strategies: drive, take a taxi, use public transit, or cycle
- Flexibility: you pick a strategy based on time, cost, and weather
- Interchangeability: each strategy gets you there, and you can switch without changing your travel plan
- Encapsulation: each strategy handles its own details, such as traffic routes or ticket purchases
Key concepts
Two implementation approaches
The Strategy pattern has two main implementation styles:
- Template-Based (Compile-Time). Algorithms are selected through C++ templates, and the type is known at compile time.
- Runtime Selection (Dynamic). Algorithms are selected at runtime through virtual functions or function pointers, and the type is determined at execution time.
Encapsulation levels
- Algorithm encapsulation: each algorithm is a separate class or template
- Context independence: the client using a strategy does not need to know about other strategies
- Behavior variation: different strategies can have completely different internal interfaces
Trade-offs
| Aspect | Benefit | Cost |
|---|---|---|
| Algorithm isolation | Each algorithm is independent and testable | More classes/code structures |
| Runtime flexibility | Switch algorithms at runtime (dynamic approach) | Virtual function overhead, pointer indirection |
| Compile-time safety | Type-safe, zero runtime overhead (template approach) | Algorithm must be known at compile-time, template code duplication |
| Extensibility | Adding algorithms doesn't modify existing code | Requires creating new strategy classes |
| Testability | Algorithms can be tested in isolation | More complex object graphs to set up |
When to use strategy
Good use cases:
- Multiple algorithms for the same task, such as sorting, compression, or serialization
- Algorithm selection depends on runtime data or configuration
- You need to switch algorithms dynamically based on performance or behavior requirements
- You want to avoid conditional logic in client code
- Testing algorithms in isolation matters
Consider the template approach when:
- The algorithm is determined at compile time
- You need a zero-cost abstraction with no virtual function overhead
- Performance is critical
- All code paths are known at compile time
Consider runtime selection when:
- The algorithm must be chosen at runtime
- Configuration files or environment variables determine the strategy
- User input determines which algorithm to use
- You have a plugin architecture where strategies are loaded dynamically
Avoid it when:
- Only one algorithm exists
- Algorithms rarely change
- The context is simple and conditional logic is clearer than the abstraction
Related patterns
- State: similar structure, but it changes object behavior based on internal state
- Decorator: also wraps behavior, but adds functionality. Strategy swaps entire algorithms.
- Factory: often used with Strategy to instantiate the right strategy
- Template Method: the opposite approach. The base class defines the algorithm structure and subclasses fill in the steps.
Implementation styles
Which style you use depends on your needs:
- Policy Based Design. An advanced C++ technique using templates, where strategies are template parameters (traits or policies).
- Template-Based Selection. Compile-time strategy selection through template specialization.
- Runtime Selection. Virtual functions or function pointers for runtime strategy selection.
Summary
Use Strategy when a class has a growing switch over algorithms. Templates win when the algorithm is fixed at compile time. Virtual functions win when configuration or user input picks it. Policy Based Design goes further, but it fits library code that can afford the compile-time cost.