1. The Problem
Problem: Multiple Ways to Do the Same Thing
Software frequently needs to support several interchangeable algorithms. Example: compressing files using ZIP, RAR, or GZIP.
Hard-coding these variations leads to rigid, branching-heavy code:
if type == "zip": ...
elif type == "rar": ...
elif type == "gzip": ...
This creates a maintenance burden, violates the Open/Closed Principle, and becomes unscalable as options grow. We need interchangeable algorithms without modifying the code that uses them.
2. The Strategy Pattern: Decouple What You Do from How You Do It
The Strategy Pattern solves this by separating behavior from implementation.
Core idea:
- Define a family of algorithms
- Encapsulate each one
- Swap them at runtime
- Keep the calling code unchanged
A system using Strategy has two parts:
- Strategy — an interface representing the operation
- Context — the class that uses a strategy and delegates work to it
The context only sees the abstract algorithm, not the concrete type. You get:
- No conditionals
- Runtime swapping
- Pure polymorphism
- Clean extensibility
Use Strategy when:
- Several algorithms solve the same problem
- Algorithms evolve or change independently
- You want runtime configurability
- You want to eliminate branching
- You want to add new algorithms without touching existing code
3. Implementation: Strategy + Context in Python
Strategy interface:
from abc import ABC, abstractmethod
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, data) -> str:
pass
Concrete strategies:
class ZipCompressionStrategy(CompressionStrategy):
def compress(self, data) -> str:
return f"Data compressed using ZIP: {data}"
class RarCompressionStrategy(CompressionStrategy):
def compress(self, data) -> str:
return f"Data compressed using RAR: {data}"
class GZipCompressionStrategy(CompressionStrategy):
def compress(self, data) -> str:
return f"Data compressed using GZIP: {data}"
Context that delegates to a strategy:
class Compressor:
def __init__(self, strategy: CompressionStrategy):
self._strategy = strategy
def set_strategy(self, strategy: CompressionStrategy):
self._strategy = strategy
def compress_data(self, data) -> str:
return self._strategy.compress(data)
Runtime swapping:
context = Compressor(ZipCompressionStrategy())
context.set_strategy(RarCompressionStrategy())
4. When to Use Strategy (and When Not To)
You likely need Strategy when you notice:
- “If mode == X then run algorithm X.”
- “These functions do the same thing but differently.”
- “I want to change behavior without editing existing code.”
- “I want to inject behavior via config/runtime.”
- “Branching is making the code harder to test.”
Common pitfalls:
- Too many tiny strategies → class explosion
- Almost identical strategies → missing abstraction
- Strategies depending on context internals → tight coupling
- Using Strategy when a simple function pointer would do → unnecessary complexity