13 Strategy Design Pattern
13.1 Overview
The Strategy Pattern is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable at runtime.
13.2 The Problem It Solves
Imagine you have multiple ways to do the same task (e.g., different image compression algorithms, different sorting methods, different payment methods). Without Strategy pattern, you’d end up with lots of if-else statements:
# ❌ Without Strategy Pattern - messy and hard to extend
def process_image(image, algorithm_type):
if algorithm_type == "jpeg":
# 50 lines of JPEG compression
pass
elif algorithm_type == "png":
# 50 lines of PNG compression
pass
elif algorithm_type == "webp":
# 50 lines of WebP compression
pass
# Adding new algorithm = modifying this function!13.3 Pattern Structure (From Your Diagram)
┌─────────────────────────────────────────────────────────────────────┐
│ STRATEGY PATTERN │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ Context │ <<interface>>
├──────────────────────┤ ◇──────▶│ Strategy │
│ - strategy │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
├──────────────────────┤ │ + execute(data) │
│ + setStrategy() │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
│ + doSomething() │ △
└──────────────────────┘ │
│ │
│ ┌────────────┴────────────┐
│ │ │
▼ ┌──────┴───────┐ ┌───────┴──────┐
strategy.execute() │ ConcreteA │ │ ConcreteB │
├──────────────┤ ├──────────────┤
│ + execute() │ │ + execute() │
└──────────────┘ └──────────────┘
◇────▶ = "has-a" (composition)
△
│ = "implements" (inheritance)
13.3.1 Key Components:
| # | Component | Role |
|---|---|---|
| 1 | Context | Maintains reference to a strategy; delegates work to it |
| 2 | Strategy (Interface) | Common interface for all algorithms |
| 3 | ConcreteStrategies | Actual algorithm implementations |
| 4 | Client | Creates strategy objects and passes to context |
| 5 | Usage Code | Shows runtime swapping of strategies |
13.4 Python Example: Medical Image Compression
Here’s a practical example relevant to radiology:
from abc import ABC, abstractmethod
from dataclasses import dataclass
# ═══════════════════════════════════════════════════════════════
# 2. STRATEGY INTERFACE
# ═══════════════════════════════════════════════════════════════
class CompressionStrategy(ABC):
"""Abstract base class defining the strategy interface."""
@abstractmethod
def compress(self, image_data: bytes) -> bytes:
"""Compress the image data."""
pass
@abstractmethod
def get_name(self) -> str:
"""Return strategy name."""
pass
# ═══════════════════════════════════════════════════════════════
# 3. CONCRETE STRATEGIES
# ═══════════════════════════════════════════════════════════════
class LosslessCompression(CompressionStrategy):
"""For diagnostic images - no quality loss."""
def compress(self, image_data: bytes) -> bytes:
print(" → Applying LOSSLESS compression (PNG-like)")
print(" Preserving all pixel data for diagnosis")
# Simulated compression
return b"LOSSLESS:" + image_data
def get_name(self) -> str:
return "Lossless (Diagnostic Quality)"
class LossyCompression(CompressionStrategy):
"""For previews/thumbnails - smaller size."""
def __init__(self, quality: int = 80):
self.quality = quality
def compress(self, image_data: bytes) -> bytes:
print(f" → Applying LOSSY compression (quality={self.quality}%)")
print(" Reducing size for quick preview")
return f"LOSSY_{self.quality}:".encode() + image_data[:len(image_data)//2]
def get_name(self) -> str:
return f"Lossy (Quality: {self.quality}%)"
class DICOMCompression(CompressionStrategy):
"""DICOM-specific compression preserving metadata."""
def compress(self, image_data: bytes) -> bytes:
print(" → Applying DICOM compression")
print(" Preserving DICOM headers and metadata")
return b"DICOM:" + image_data
def get_name(self) -> str:
return "DICOM Standard"
# ═══════════════════════════════════════════════════════════════
# 1. CONTEXT
# ═══════════════════════════════════════════════════════════════
class ImageProcessor:
"""Context class that uses a compression strategy."""
def __init__(self):
self._strategy: CompressionStrategy | None = None
def set_strategy(self, strategy: CompressionStrategy) -> None:
"""Set the compression strategy at runtime."""
self._strategy = strategy
print(f"\n📋 Strategy set to: {strategy.get_name()}")
def process_image(self, image_data: bytes) -> bytes:
"""Process image using the current strategy."""
if self._strategy is None:
raise ValueError("No compression strategy set!")
print(f"🖼️ Processing image ({len(image_data)} bytes)...")
result = self._strategy.compress(image_data)
print(f"✅ Done! Output: {len(result)} bytes\n")
return result
# ═══════════════════════════════════════════════════════════════
# 4 & 5. CLIENT CODE
# ═══════════════════════════════════════════════════════════════
def main():
# Sample image data
ct_scan_data = b"RAW_CT_SCAN_PIXEL_DATA_HERE..." * 100
# Create the context
processor = ImageProcessor()
# Scenario 1: Archiving for PACS - use lossless
print("=" * 50)
print("SCENARIO 1: Archiving CT scan to PACS")
print("=" * 50)
lossless = LosslessCompression() # Create strategy
processor.set_strategy(lossless) # Set strategy
archived = processor.process_image(ct_scan_data) # Execute
# Scenario 2: Creating thumbnail preview - use lossy
print("=" * 50)
print("SCENARIO 2: Creating thumbnail for web viewer")
print("=" * 50)
lossy = LossyCompression(quality=60) # Create different strategy
processor.set_strategy(lossy) # Swap strategy at runtime!
thumbnail = processor.process_image(ct_scan_data)
# Scenario 3: Export for another hospital - use DICOM
print("=" * 50)
print("SCENARIO 3: Exporting for external consultation")
print("=" * 50)
dicom = DICOMCompression()
processor.set_strategy(dicom)
exported = processor.process_image(ct_scan_data)
if __name__ == "__main__":
main()13.5 Output
==================================================
SCENARIO 1: Archiving CT scan to PACS
==================================================
📋 Strategy set to: Lossless (Diagnostic Quality)
🖼️ Processing image (3100 bytes)...
→ Applying LOSSLESS compression (PNG-like)
Preserving all pixel data for diagnosis
✅ Done! Output: 3109 bytes
==================================================
SCENARIO 2: Creating thumbnail for web viewer
==================================================
📋 Strategy set to: Lossy (Quality: 60%)
🖼️ Processing image (3100 bytes)...
→ Applying LOSSY compression (quality=60%)
Reducing size for quick preview
✅ Done! Output: 1558 bytes
==================================================
SCENARIO 3: Exporting for external consultation
==================================================
📋 Strategy set to: DICOM Standard
🖼️ Processing image (3100 bytes)...
→ Applying DICOM compression
Preserving DICOM headers and metadata
✅ Done! Output: 3106 bytes
13.6 Key Benefits
┌────────────────────────────────────────────────────────────────┐
│ WHY USE STRATEGY? │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. OPEN/CLOSED PRINCIPLE │
│ ───────────────────── │
│ Add new algorithms WITHOUT modifying existing code │
│ │
│ # Just create a new class: │
│ class AIEnhancedCompression(CompressionStrategy): │
│ def compress(self, data): ... │
│ │
├────────────────────────────────────────────────────────────────┤
│ │
│ 2. RUNTIME FLEXIBILITY │
│ ─────────────────── │
│ Switch algorithms on-the-fly based on conditions │
│ │
│ if image.is_diagnostic: │
│ processor.set_strategy(LosslessCompression()) │
│ else: │
│ processor.set_strategy(LossyCompression(50)) │
│ │
├────────────────────────────────────────────────────────────────┤
│ │
│ 3. ELIMINATES CONDITIONALS │
│ ─────────────────────── │
│ No more giant if-elif-else chains │
│ │
├────────────────────────────────────────────────────────────────┤
│ │
│ 4. EASIER TESTING │
│ ────────────── │
│ Test each strategy in isolation │
│ │
└────────────────────────────────────────────────────────────────┘
13.7 When to Use Strategy Pattern
| ✅ Use When | ❌ Avoid When |
|---|---|
| Multiple algorithms for same task | Only 1-2 simple algorithms |
| Need runtime algorithm switching | Algorithm never changes |
| Algorithms change independently | Algorithms are trivially simple |
| Want to avoid conditionals | Overhead not justified |