Strategy Pattern
Strategy Pattern: Swapping Algorithms at Runtime
Section titled “Strategy Pattern: Swapping Algorithms at Runtime”Now let’s dive into the Strategy Pattern - one of the most practical behavioral design patterns that enables you to define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.
Why Strategy Pattern?
Section titled “Why Strategy Pattern?”Imagine you’re using a GPS navigation app. You can choose different routes - fastest, shortest, avoid tolls, scenic route. Each routing algorithm is different, but they all solve the same problem: getting you from A to B. The Strategy Pattern works the same way!
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it, enabling you to swap behaviors at runtime without changing the client code.
What’s the Use of Strategy Pattern?
Section titled “What’s the Use of Strategy Pattern?”The Strategy Pattern is useful when:
- You have multiple algorithms for a specific task and want to switch between them
- You want to avoid conditionals - No more if/else or switch statements for algorithm selection
- You need runtime flexibility - Change algorithm without modifying client code
- You want to isolate algorithm code - Each strategy is in its own class
- You need to test algorithms independently - Easy to unit test each strategy
What Happens If We Don’t Use Strategy Pattern?
Section titled “What Happens If We Don’t Use Strategy Pattern?”Without the Strategy Pattern, you might:
- Use massive if/else chains - Hard to maintain and extend
- Violate Open/Closed Principle - Need to modify code to add new algorithms
- Duplicate code - Similar algorithms with slight variations
- Tight coupling - Client knows about all algorithm implementations
- Hard to test - Algorithms mixed with business logic
Simple Example: The Sorting Application
Section titled “Simple Example: The Sorting Application”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Strategy Pattern works in practice - showing how strategies are swapped at runtime:
The Problem
Section titled “The Problem”You’re building a sorting utility that needs to support multiple sorting algorithms. Without Strategy Pattern:
# ❌ Without Strategy Pattern - Massive if/else chain!
from typing import List
class SortingApplication: def __init__(self): self.algorithm = "quicksort" # Default algorithm
def set_algorithm(self, algorithm: str): self.algorithm = algorithm
def sort(self, data: List[int]) -> List[int]: # Problem: Massive if/else chain! if self.algorithm == "bubble": # Bubble sort implementation result = data.copy() n = len(result) for i in range(n): for j in range(0, n - i - 1): if result[j] > result[j + 1]: result[j], result[j + 1] = result[j + 1], result[j] return result
elif self.algorithm == "quick": # Quick sort implementation def quicksort(arr): if len(arr) <= 1: return arr pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quicksort(left) + middle + quicksort(right) return quicksort(data)
elif self.algorithm == "merge": # Merge sort implementation def mergesort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 left = mergesort(arr[:mid]) right = mergesort(arr[mid:]) return merge(left, right)
def merge(left, right): result = [] i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 result.extend(left[i:]) result.extend(right[j:]) return result
return mergesort(data)
else: raise ValueError(f"Unknown algorithm: {self.algorithm}")
# Problems: # - Adding new algorithm requires modifying this class # - Violates Open/Closed Principle # - Hard to test individual algorithms # - Code is hard to read and maintain
# Usageapp = SortingApplication()app.set_algorithm("bubble")print(app.sort([3, 1, 4, 1, 5]))// ❌ Without Strategy Pattern - Massive if/else chain!
import java.util.*;
public class SortingApplication { private String algorithm = "quicksort"; // Default algorithm
public void setAlgorithm(String algorithm) { this.algorithm = algorithm; }
public int[] sort(int[] data) { // Problem: Massive if/else chain! if (algorithm.equals("bubble")) { // Bubble sort implementation int[] result = data.clone(); int n = result.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { int temp = result[j]; result[j] = result[j + 1]; result[j + 1] = temp; } } } return result; } else if (algorithm.equals("quick")) { // Quick sort implementation int[] result = data.clone(); quickSort(result, 0, result.length - 1); return result; } else if (algorithm.equals("merge")) { // Merge sort implementation int[] result = data.clone(); mergeSort(result, 0, result.length - 1); return result; } else { throw new IllegalArgumentException("Unknown algorithm: " + algorithm); }
// Problems: // - Adding new algorithm requires modifying this class // - Violates Open/Closed Principle // - Hard to test individual algorithms // - Code is hard to read and maintain }
private void quickSort(int[] arr, int low, int high) { // QuickSort implementation if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } }
private int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; }
private void mergeSort(int[] arr, int l, int r) { // MergeSort implementation if (l < r) { int m = l + (r - l) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } }
private void merge(int[] arr, int l, int m, int r) { // Merge two subarrays int n1 = m - l + 1; int n2 = r - m; int[] L = new int[n1]; int[] R = new int[n2];
System.arraycopy(arr, l, L, 0, n1); System.arraycopy(arr, m + 1, R, 0, n2);
int i = 0, j = 0, k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; }}
// Usagepublic class Main { public static void main(String[] args) { SortingApplication app = new SortingApplication(); app.setAlgorithm("bubble"); System.out.println(Arrays.toString(app.sort(new int[]{3, 1, 4, 1, 5}))); }}// ❌ Without Strategy Pattern - Massive if/else chain!
class SortingApplication { private algorithm: string = "quicksort"; // Default algorithm
setAlgorithm(algorithm: string): void { this.algorithm = algorithm; }
sort(data: number[]): number[] { // Problem: Massive if/else chain! if (this.algorithm === "bubble") { // Bubble sort implementation const result = [...data]; const n = result.length; for (let i = 0; i < n; i++) { for (let j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { [result[j], result[j + 1]] = [result[j + 1], result[j]]; } } } return result; } else if (this.algorithm === "quick") { // Quick sort implementation const quicksort = (arr: number[]): number[] => { if (arr.length <= 1) return arr; const pivot = arr[Math.floor(arr.length / 2)]; const left = arr.filter(x => x < pivot); const middle = arr.filter(x => x === pivot); const right = arr.filter(x => x > pivot); return [...quicksort(left), ...middle, ...quicksort(right)]; }; return quicksort(data); } else if (this.algorithm === "merge") { // Merge sort implementation const mergesort = (arr: number[]): number[] => { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergesort(arr.slice(0, mid)); const right = mergesort(arr.slice(mid)); return merge(left, right); };
const merge = (left: number[], right: number[]): number[] => { const result: number[] = []; let i = 0, j = 0; while (i < left.length && j < right.length) { if (left[i] <= right[j]) { result.push(left[i++]); } else { result.push(right[j++]); } } return [...result, ...left.slice(i), ...right.slice(j)]; };
return mergesort(data); } else { throw new Error(`Unknown algorithm: ${this.algorithm}`); }
// Problems: // - Adding new algorithm requires modifying this class // - Violates Open/Closed Principle // - Hard to test individual algorithms // - Code is hard to read and maintain }}
// Usageconst app = new SortingApplication();app.setAlgorithm("bubble");console.log(app.sort([3, 1, 4, 1, 5]));// ❌ Without Strategy Pattern - Massive if/else chain!
#include <iostream>#include <vector>#include <algorithm>#include <stdexcept>
class SortingApplication {private: std::string algorithm = "quicksort"; // Default algorithm
std::vector<int> quicksort(std::vector<int> arr) { if (arr.size() <= 1) return arr; int pivot = arr[arr.size() / 2]; std::vector<int> left, middle, right; for (int x : arr) { if (x < pivot) left.push_back(x); else if (x == pivot) middle.push_back(x); else right.push_back(x); } std::vector<int> result; auto leftSorted = quicksort(left); auto rightSorted = quicksort(right); result.insert(result.end(), leftSorted.begin(), leftSorted.end()); result.insert(result.end(), middle.begin(), middle.end()); result.insert(result.end(), rightSorted.begin(), rightSorted.end()); return result; }
std::vector<int> mergesort(std::vector<int> arr) { if (arr.size() <= 1) return arr; int mid = arr.size() / 2; std::vector<int> left(arr.begin(), arr.begin() + mid); std::vector<int> right(arr.begin() + mid, arr.end()); return merge(mergesort(left), mergesort(right)); }
std::vector<int> merge(std::vector<int> left, std::vector<int> right) { std::vector<int> result; size_t i = 0, j = 0; while (i < left.size() && j < right.size()) { if (left[i] <= right[j]) { result.push_back(left[i++]); } else { result.push_back(right[j++]); } } result.insert(result.end(), left.begin() + i, left.end()); result.insert(result.end(), right.begin() + j, right.end()); return result; }
public: void setAlgorithm(const std::string& algo) { algorithm = algo; }
std::vector<int> sort(const std::vector<int>& data) { // Problem: Massive if/else chain! if (algorithm == "bubble") { // Bubble sort implementation std::vector<int> result = data; int n = result.size(); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { std::swap(result[j], result[j + 1]); } } } return result; } else if (algorithm == "quick") { return quicksort(data); } else if (algorithm == "merge") { return mergesort(data); } else { throw std::invalid_argument("Unknown algorithm: " + algorithm); }
// Problems: // - Adding new algorithm requires modifying this class // - Violates Open/Closed Principle // - Hard to test individual algorithms // - Code is hard to read and maintain }};
// Usageint main() { SortingApplication app; app.setAlgorithm("bubble"); auto result = app.sort({3, 1, 4, 1, 5}); for (int x : result) { std::cout << x << " "; } return 0;}// ❌ Without Strategy Pattern - Massive if/else chain!
using System;using System.Linq;using System.Collections.Generic;
public class SortingApplication{ private string algorithm = "quicksort"; // Default algorithm
public void SetAlgorithm(string algorithm) { this.algorithm = algorithm; }
public int[] Sort(int[] data) { // Problem: Massive if/else chain! if (algorithm == "bubble") { // Bubble sort implementation int[] result = (int[])data.Clone(); int n = result.Length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { int temp = result[j]; result[j] = result[j + 1]; result[j + 1] = temp; } } } return result; } else if (algorithm == "quick") { // Quick sort implementation int[] Quicksort(int[] arr) { if (arr.Length <= 1) return arr; int pivot = arr[arr.Length / 2]; var left = arr.Where(x => x < pivot).ToArray(); var middle = arr.Where(x => x == pivot).ToArray(); var right = arr.Where(x => x > pivot).ToArray(); return Quicksort(left).Concat(middle).Concat(Quicksort(right)).ToArray(); } return Quicksort(data); } else if (algorithm == "merge") { // Merge sort implementation int[] Mergesort(int[] arr) { if (arr.Length <= 1) return arr; int mid = arr.Length / 2; int[] left = arr.Take(mid).ToArray(); int[] right = arr.Skip(mid).ToArray(); return Merge(Mergesort(left), Mergesort(right)); }
int[] Merge(int[] left, int[] right) { List<int> result = new List<int>(); int i = 0, j = 0; while (i < left.Length && j < right.Length) { if (left[i] <= right[j]) { result.Add(left[i++]); } else { result.Add(right[j++]); } } result.AddRange(left.Skip(i)); result.AddRange(right.Skip(j)); return result.ToArray(); }
return Mergesort(data); } else { throw new ArgumentException($"Unknown algorithm: {algorithm}"); }
// Problems: // - Adding new algorithm requires modifying this class // - Violates Open/Closed Principle // - Hard to test individual algorithms // - Code is hard to read and maintain }}
// Usageclass Program{ static void Main() { SortingApplication app = new SortingApplication(); app.SetAlgorithm("bubble"); Console.WriteLine(string.Join(", ", app.Sort(new int[] { 3, 1, 4, 1, 5 }))); }}package main
import ( "fmt" "sort")
// ❌ Without Strategy Pattern - if/else nightmare!type SortingApplication struct { algorithm string}
func (s *SortingApplication) SetAlgorithm(algo string) { s.algorithm = algo }
func (s *SortingApplication) Sort(data []int) []int { result := make([]int, len(data)) copy(result, data) if s.algorithm == "bubble" { for i := 0; i < len(result)-1; i++ { for j := 0; j < len(result)-1-i; j++ { if result[j] > result[j+1] { result[j], result[j+1] = result[j+1], result[j] } } } } else if s.algorithm == "quick" { sort.Ints(result) // simplified } else { panic("Unknown algorithm: " + s.algorithm) } // Problems: Adding new algorithm requires modifying this class! return result}
func main() { app := &SortingApplication{} app.SetAlgorithm("bubble") fmt.Println(app.Sort([]int{3, 1, 4, 1, 5}))}// The Problemfn shipping_cost(kind: &str, weight: f64) -> f64 { if kind == "standard" { weight * 5.0 } else if kind == "express" { weight * 12.0 } else { 0.0 }}Problems:
- Massive
if/elsechain - Hard to read and maintain - Violates Open/Closed Principle - Need to modify class to add algorithms
- Hard to test - All algorithms in one class
- Tight coupling - Client knows about algorithm details
The Solution: Strategy Pattern
Section titled “The Solution: Strategy Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the Strategy interfaceclass SortStrategy(ABC): """Strategy interface for sorting algorithms"""
@abstractmethod def sort(self, data: List[int]) -> List[int]: """Sort the data and return sorted list""" pass
# Step 2: Implement Concrete Strategiesclass BubbleSortStrategy(SortStrategy): """Bubble sort strategy - O(n²) but simple"""
def sort(self, data: List[int]) -> List[int]: result = data.copy() n = len(result) for i in range(n): for j in range(0, n - i - 1): if result[j] > result[j + 1]: result[j], result[j + 1] = result[j + 1], result[j] print("📊 Using Bubble Sort (O(n²) - good for small datasets)") return result
class QuickSortStrategy(SortStrategy): """Quick sort strategy - O(n log n) average"""
def sort(self, data: List[int]) -> List[int]: def quicksort(arr: List[int]) -> List[int]: if len(arr) <= 1: return arr pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quicksort(left) + middle + quicksort(right)
print("⚡ Using Quick Sort (O(n log n) - fast for most cases)") return quicksort(data)
class MergeSortStrategy(SortStrategy): """Merge sort strategy - O(n log n) guaranteed"""
def sort(self, data: List[int]) -> List[int]: def mergesort(arr: List[int]) -> List[int]: if len(arr) <= 1: return arr mid = len(arr) // 2 left = mergesort(arr[:mid]) right = mergesort(arr[mid:]) return self._merge(left, right)
print("🔀 Using Merge Sort (O(n log n) - stable, predictable)") return mergesort(data)
def _merge(self, left: List[int], right: List[int]) -> List[int]: result = [] i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 result.extend(left[i:]) result.extend(right[j:]) return result
# Step 3: Create the Context classclass SortingApplication: """Context class that uses sorting strategies"""
def __init__(self, strategy: SortStrategy = None): self._strategy = strategy or QuickSortStrategy() # Default strategy
def set_strategy(self, strategy: SortStrategy) -> None: """Change the sorting strategy at runtime""" self._strategy = strategy print(f"✅ Strategy changed to: {strategy.__class__.__name__}")
def sort(self, data: List[int]) -> List[int]: """Sort data using the current strategy""" print(f"\n🔄 Sorting {data}") result = self._strategy.sort(data) print(f"✨ Result: {result}") return result
# Step 4: Use the patterndef main(): # Create context with default strategy app = SortingApplication()
data = [64, 34, 25, 12, 22, 11, 90]
# Sort with default (QuickSort) app.sort(data)
# Switch to BubbleSort app.set_strategy(BubbleSortStrategy()) app.sort(data)
# Switch to MergeSort app.set_strategy(MergeSortStrategy()) app.sort(data)
print("\n✅ Strategy Pattern: Algorithms swapped at runtime!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Strategy interfaceinterface SortStrategy { /** * Strategy interface for sorting algorithms */ int[] sort(int[] data);}
// Step 2: Implement Concrete Strategiesclass BubbleSortStrategy implements SortStrategy { /** * Bubble sort strategy - O(n²) but simple */ @Override public int[] sort(int[] data) { int[] result = data.clone(); int n = result.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { int temp = result[j]; result[j] = result[j + 1]; result[j + 1] = temp; } } } System.out.println("📊 Using Bubble Sort (O(n²) - good for small datasets)"); return result; }}
class QuickSortStrategy implements SortStrategy { /** * Quick sort strategy - O(n log n) average */ @Override public int[] sort(int[] data) { int[] result = data.clone(); quickSort(result, 0, result.length - 1); System.out.println("⚡ Using Quick Sort (O(n log n) - fast for most cases)"); return result; }
private void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } }
private int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; }}
class MergeSortStrategy implements SortStrategy { /** * Merge sort strategy - O(n log n) guaranteed */ @Override public int[] sort(int[] data) { int[] result = data.clone(); mergeSort(result, 0, result.length - 1); System.out.println("🔀 Using Merge Sort (O(n log n) - stable, predictable)"); return result; }
private void mergeSort(int[] arr, int l, int r) { if (l < r) { int m = l + (r - l) / 2; mergeSort(arr, l, m); mergeSort(arr, m + 1, r); merge(arr, l, m, r); } }
private void merge(int[] arr, int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; int[] L = new int[n1]; int[] R = new int[n2];
System.arraycopy(arr, l, L, 0, n1); System.arraycopy(arr, m + 1, R, 0, n2);
int i = 0, j = 0, k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; }}
// Step 3: Create the Context classclass SortingApplication { /** * Context class that uses sorting strategies */ private SortStrategy strategy;
public SortingApplication() { this.strategy = new QuickSortStrategy(); // Default strategy }
public SortingApplication(SortStrategy strategy) { this.strategy = strategy; }
public void setStrategy(SortStrategy strategy) { // Change the sorting strategy at runtime this.strategy = strategy; System.out.println("✅ Strategy changed to: " + strategy.getClass().getSimpleName()); }
public int[] sort(int[] data) { // Sort data using the current strategy System.out.println("\n🔄 Sorting " + Arrays.toString(data)); int[] result = strategy.sort(data); System.out.println("✨ Result: " + Arrays.toString(result)); return result; }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { // Create context with default strategy SortingApplication app = new SortingApplication();
int[] data = {64, 34, 25, 12, 22, 11, 90};
// Sort with default (QuickSort) app.sort(data);
// Switch to BubbleSort app.setStrategy(new BubbleSortStrategy()); app.sort(data);
// Switch to MergeSort app.setStrategy(new MergeSortStrategy()); app.sort(data);
System.out.println("\n✅ Strategy Pattern: Algorithms swapped at runtime!"); }}// Step 1: Define the Strategy interfaceinterface SortStrategy { /** Strategy interface for sorting algorithms */ sort(data: number[]): number[];}
// Step 2: Implement Concrete Strategiesclass BubbleSortStrategy implements SortStrategy { /** Bubble sort strategy - O(n²) but simple */ sort(data: number[]): number[] { const result = [...data]; const n = result.length; for (let i = 0; i < n - 1; i++) { for (let j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { [result[j], result[j + 1]] = [result[j + 1], result[j]]; } } } console.log("📊 Using Bubble Sort (O(n²) - good for small datasets)"); return result; }}
class QuickSortStrategy implements SortStrategy { /** Quick sort strategy - O(n log n) average */ sort(data: number[]): number[] { const quicksort = (arr: number[]): number[] => { if (arr.length <= 1) return arr; const pivot = arr[Math.floor(arr.length / 2)]; const left = arr.filter(x => x < pivot); const middle = arr.filter(x => x === pivot); const right = arr.filter(x => x > pivot); return [...quicksort(left), ...middle, ...quicksort(right)]; };
console.log("⚡ Using Quick Sort (O(n log n) - fast for most cases)"); return quicksort(data); }}
class MergeSortStrategy implements SortStrategy { /** Merge sort strategy - O(n log n) guaranteed */ sort(data: number[]): number[] { const mergesort = (arr: number[]): number[] => { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergesort(arr.slice(0, mid)); const right = mergesort(arr.slice(mid)); return this.merge(left, right); };
console.log("🔀 Using Merge Sort (O(n log n) - stable, predictable)"); return mergesort(data); }
private merge(left: number[], right: number[]): number[] { const result: number[] = []; let i = 0, j = 0; while (i < left.length && j < right.length) { if (left[i] <= right[j]) { result.push(left[i++]); } else { result.push(right[j++]); } } return [...result, ...left.slice(i), ...right.slice(j)]; }}
// Step 3: Create the Context classclass SortingApplication { /** Context class that uses sorting strategies */ private strategy: SortStrategy;
constructor(strategy: SortStrategy = new QuickSortStrategy()) { this.strategy = strategy; }
setStrategy(strategy: SortStrategy): void { /** Change the sorting strategy at runtime */ this.strategy = strategy; console.log(`✅ Strategy changed to: ${strategy.constructor.name}`); }
sort(data: number[]): number[] { /** Sort data using the current strategy */ console.log(`\n🔄 Sorting ${data}`); const result = this.strategy.sort(data); console.log(`✨ Result: ${result}`); return result; }}
// Step 4: Use the patternfunction main(): void { // Create context with default strategy const app = new SortingApplication();
const data = [64, 34, 25, 12, 22, 11, 90];
// Sort with default (QuickSort) app.sort(data);
// Switch to BubbleSort app.setStrategy(new BubbleSortStrategy()); app.sort(data);
// Switch to MergeSort app.setStrategy(new MergeSortStrategy()); app.sort(data);
console.log("\n✅ Strategy Pattern: Algorithms swapped at runtime!");}
main();#include <iostream>#include <vector>#include <algorithm>#include <memory>
// Step 1: Define the Strategy interfaceclass SortStrategy {public: virtual ~SortStrategy() = default; /** Strategy interface for sorting algorithms */ virtual std::vector<int> sort(const std::vector<int>& data) = 0; virtual std::string getName() const = 0;};
// Step 2: Implement Concrete Strategiesclass BubbleSortStrategy : public SortStrategy {public: /** Bubble sort strategy - O(n²) but simple */ std::vector<int> sort(const std::vector<int>& data) override { std::vector<int> result = data; int n = result.size(); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { std::swap(result[j], result[j + 1]); } } } std::cout << "📊 Using Bubble Sort (O(n²) - good for small datasets)" << std::endl; return result; }
std::string getName() const override { return "BubbleSortStrategy"; }};
class QuickSortStrategy : public SortStrategy {public: /** Quick sort strategy - O(n log n) average */ std::vector<int> sort(const std::vector<int>& data) override { std::cout << "⚡ Using Quick Sort (O(n log n) - fast for most cases)" << std::endl; return quicksort(data); }
std::string getName() const override { return "QuickSortStrategy"; }
private: std::vector<int> quicksort(const std::vector<int>& arr) { if (arr.size() <= 1) return arr; int pivot = arr[arr.size() / 2]; std::vector<int> left, middle, right; for (int x : arr) { if (x < pivot) left.push_back(x); else if (x == pivot) middle.push_back(x); else right.push_back(x); } std::vector<int> result; auto leftSorted = quicksort(left); auto rightSorted = quicksort(right); result.insert(result.end(), leftSorted.begin(), leftSorted.end()); result.insert(result.end(), middle.begin(), middle.end()); result.insert(result.end(), rightSorted.begin(), rightSorted.end()); return result; }};
class MergeSortStrategy : public SortStrategy {public: /** Merge sort strategy - O(n log n) guaranteed */ std::vector<int> sort(const std::vector<int>& data) override { std::cout << "🔀 Using Merge Sort (O(n log n) - stable, predictable)" << std::endl; return mergesort(data); }
std::string getName() const override { return "MergeSortStrategy"; }
private: std::vector<int> mergesort(const std::vector<int>& arr) { if (arr.size() <= 1) return arr; int mid = arr.size() / 2; std::vector<int> left(arr.begin(), arr.begin() + mid); std::vector<int> right(arr.begin() + mid, arr.end()); return merge(mergesort(left), mergesort(right)); }
std::vector<int> merge(const std::vector<int>& left, const std::vector<int>& right) { std::vector<int> result; size_t i = 0, j = 0; while (i < left.size() && j < right.size()) { if (left[i] <= right[j]) { result.push_back(left[i++]); } else { result.push_back(right[j++]); } } result.insert(result.end(), left.begin() + i, left.end()); result.insert(result.end(), right.begin() + j, right.end()); return result; }};
// Step 3: Create the Context classclass SortingApplication {private: std::unique_ptr<SortStrategy> strategy;
public: /** Context class that uses sorting strategies */ SortingApplication() : strategy(std::make_unique<QuickSortStrategy>()) {}
explicit SortingApplication(std::unique_ptr<SortStrategy> strat) : strategy(std::move(strat)) {}
void setStrategy(std::unique_ptr<SortStrategy> strat) { /** Change the sorting strategy at runtime */ strategy = std::move(strat); std::cout << "✅ Strategy changed to: " << strategy->getName() << std::endl; }
std::vector<int> sort(const std::vector<int>& data) { /** Sort data using the current strategy */ std::cout << "\n🔄 Sorting "; for (int x : data) std::cout << x << " "; std::cout << std::endl;
auto result = strategy->sort(data);
std::cout << "✨ Result: "; for (int x : result) std::cout << x << " "; std::cout << std::endl;
return result; }};
// Step 4: Use the patternint main() { // Create context with default strategy SortingApplication app;
std::vector<int> data = {64, 34, 25, 12, 22, 11, 90};
// Sort with default (QuickSort) app.sort(data);
// Switch to BubbleSort app.setStrategy(std::make_unique<BubbleSortStrategy>()); app.sort(data);
// Switch to MergeSort app.setStrategy(std::make_unique<MergeSortStrategy>()); app.sort(data);
std::cout << "\n✅ Strategy Pattern: Algorithms swapped at runtime!" << std::endl;
return 0;}using System;using System.Linq;
// Step 1: Define the Strategy interfacepublic interface ISortStrategy{ /** Strategy interface for sorting algorithms */ int[] Sort(int[] data);}
// Step 2: Implement Concrete Strategiespublic class BubbleSortStrategy : ISortStrategy{ /** Bubble sort strategy - O(n²) but simple */ public int[] Sort(int[] data) { int[] result = (int[])data.Clone(); int n = result.Length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (result[j] > result[j + 1]) { int temp = result[j]; result[j] = result[j + 1]; result[j + 1] = temp; } } } Console.WriteLine("📊 Using Bubble Sort (O(n²) - good for small datasets)"); return result; }}
public class QuickSortStrategy : ISortStrategy{ /** Quick sort strategy - O(n log n) average */ public int[] Sort(int[] data) { Console.WriteLine("⚡ Using Quick Sort (O(n log n) - fast for most cases)"); return Quicksort(data); }
private int[] Quicksort(int[] arr) { if (arr.Length <= 1) return arr; int pivot = arr[arr.Length / 2]; var left = arr.Where(x => x < pivot).ToArray(); var middle = arr.Where(x => x == pivot).ToArray(); var right = arr.Where(x => x > pivot).ToArray(); return Quicksort(left).Concat(middle).Concat(Quicksort(right)).ToArray(); }}
public class MergeSortStrategy : ISortStrategy{ /** Merge sort strategy - O(n log n) guaranteed */ public int[] Sort(int[] data) { Console.WriteLine("🔀 Using Merge Sort (O(n log n) - stable, predictable)"); return Mergesort(data); }
private int[] Mergesort(int[] arr) { if (arr.Length <= 1) return arr; int mid = arr.Length / 2; int[] left = arr.Take(mid).ToArray(); int[] right = arr.Skip(mid).ToArray(); return Merge(Mergesort(left), Mergesort(right)); }
private int[] Merge(int[] left, int[] right) { var result = new System.Collections.Generic.List<int>(); int i = 0, j = 0; while (i < left.Length && j < right.Length) { if (left[i] <= right[j]) { result.Add(left[i++]); } else { result.Add(right[j++]); } } result.AddRange(left.Skip(i)); result.AddRange(right.Skip(j)); return result.ToArray(); }}
// Step 3: Create the Context classpublic class SortingApplication{ /** Context class that uses sorting strategies */ private ISortStrategy strategy;
public SortingApplication() : this(new QuickSortStrategy()) { }
public SortingApplication(ISortStrategy strategy) { this.strategy = strategy; }
public void SetStrategy(ISortStrategy strategy) { /** Change the sorting strategy at runtime */ this.strategy = strategy; Console.WriteLine($"✅ Strategy changed to: {strategy.GetType().Name}"); }
public int[] Sort(int[] data) { /** Sort data using the current strategy */ Console.WriteLine($"\n🔄 Sorting {string.Join(", ", data)}"); int[] result = strategy.Sort(data); Console.WriteLine($"✨ Result: {string.Join(", ", result)}"); return result; }}
// Step 4: Use the patternclass Program{ static void Main() { // Create context with default strategy SortingApplication app = new SortingApplication();
int[] data = { 64, 34, 25, 12, 22, 11, 90 };
// Sort with default (QuickSort) app.Sort(data);
// Switch to BubbleSort app.SetStrategy(new BubbleSortStrategy()); app.Sort(data);
// Switch to MergeSort app.SetStrategy(new MergeSortStrategy()); app.Sort(data);
Console.WriteLine("\n✅ Strategy Pattern: Algorithms swapped at runtime!"); }}package main
import ( "fmt" "sort")
// Step 1: Strategy interfacetype SortStrategy interface { Sort(data []int) []int}
// Step 2: Concrete strategiestype BubbleSortStrategy struct{}
func (b *BubbleSortStrategy) Sort(data []int) []int { result := make([]int, len(data)) copy(result, data) fmt.Println("🫧 Using Bubble Sort") for i := 0; i < len(result)-1; i++ { for j := 0; j < len(result)-1-i; j++ { if result[j] > result[j+1] { result[j], result[j+1] = result[j+1], result[j] } } } return result}
type QuickSortStrategy struct{}
func (q *QuickSortStrategy) Sort(data []int) []int { result := make([]int, len(data)) copy(result, data) fmt.Println("⚡ Using Quick Sort") sort.Ints(result) return result}
// Step 3: Contexttype SortingApplication struct { strategy SortStrategy}
func NewSortingApplication(s SortStrategy) *SortingApplication { return &SortingApplication{strategy: s}}
func (app *SortingApplication) SetStrategy(s SortStrategy) { app.strategy = s fmt.Printf("✅ Strategy changed\n")}
func (app *SortingApplication) Sort(data []int) []int { fmt.Printf("\n🔄 Sorting %v\n", data) result := app.strategy.Sort(data) fmt.Printf("✨ Result: %v\n", result) return result}
func main() { data := []int{64, 34, 25, 12, 22, 11, 90} app := NewSortingApplication(&QuickSortStrategy{}) app.Sort(data)
app.SetStrategy(&BubbleSortStrategy{}) app.Sort(data) fmt.Println("\n✅ Strategy Pattern: Algorithms swapped at runtime!")}// Class Structuretrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}Real-World Software Example: Payment Processing System
Section titled “Real-World Software Example: Payment Processing System”Now let’s see a realistic software example - a payment processing system that supports multiple payment methods.
The Problem
Section titled “The Problem”You’re building an e-commerce checkout system that needs to support multiple payment methods - Credit Card, PayPal, and Cryptocurrency. Without Strategy Pattern:
# ❌ Without Strategy Pattern - if/else nightmare!
class PaymentProcessor: def __init__(self): self.payment_method = "credit_card"
def set_payment_method(self, method: str): self.payment_method = method
def process_payment(self, amount: float, details: dict) -> bool: # Problem: Massive if/else chain! if self.payment_method == "credit_card": card_number = details.get("card_number") cvv = details.get("cvv") expiry = details.get("expiry")
# Validate card if not card_number or len(card_number) != 16: raise ValueError("Invalid card number") if not cvv or len(cvv) != 3: raise ValueError("Invalid CVV")
# Process credit card payment print(f"💳 Processing credit card payment of ${amount}") print(f" Card: **** **** **** {card_number[-4:]}") # Connect to payment gateway... return True
elif self.payment_method == "paypal": email = details.get("email")
# Validate PayPal if not email or "@" not in email: raise ValueError("Invalid PayPal email")
# Process PayPal payment print(f"🅿️ Processing PayPal payment of ${amount}") print(f" Email: {email}") # Redirect to PayPal... return True
elif self.payment_method == "crypto": wallet_address = details.get("wallet_address") currency = details.get("currency", "BTC")
# Validate crypto if not wallet_address or len(wallet_address) < 26: raise ValueError("Invalid wallet address")
# Process crypto payment print(f"₿ Processing {currency} payment of ${amount}") print(f" Wallet: {wallet_address[:10]}...") # Generate crypto invoice... return True
else: raise ValueError(f"Unknown payment method: {self.payment_method}")
# Problems: # - Adding new payment method requires modifying this class # - Each payment method has different validation logic # - Hard to test individual payment methods # - Violates Single Responsibility Principle
# Usageprocessor = PaymentProcessor()processor.set_payment_method("credit_card")processor.process_payment(99.99, {"card_number": "1234567890123456", "cvv": "123", "expiry": "12/25"})// ❌ Without Strategy Pattern - if/else nightmare!
import java.util.Map;
public class PaymentProcessor { private String paymentMethod = "credit_card";
public void setPaymentMethod(String method) { this.paymentMethod = method; }
public boolean processPayment(double amount, Map<String, String> details) { // Problem: Massive if/else chain! if (paymentMethod.equals("credit_card")) { String cardNumber = details.get("card_number"); String cvv = details.get("cvv"); String expiry = details.get("expiry");
// Validate card if (cardNumber == null || cardNumber.length() != 16) { throw new IllegalArgumentException("Invalid card number"); } if (cvv == null || cvv.length() != 3) { throw new IllegalArgumentException("Invalid CVV"); }
// Process credit card payment System.out.println("💳 Processing credit card payment of $" + amount); System.out.println(" Card: **** **** **** " + cardNumber.substring(12)); // Connect to payment gateway... return true;
} else if (paymentMethod.equals("paypal")) { String email = details.get("email");
// Validate PayPal if (email == null || !email.contains("@")) { throw new IllegalArgumentException("Invalid PayPal email"); }
// Process PayPal payment System.out.println("🅿️ Processing PayPal payment of $" + amount); System.out.println(" Email: " + email); // Redirect to PayPal... return true;
} else if (paymentMethod.equals("crypto")) { String walletAddress = details.get("wallet_address"); String currency = details.getOrDefault("currency", "BTC");
// Validate crypto if (walletAddress == null || walletAddress.length() < 26) { throw new IllegalArgumentException("Invalid wallet address"); }
// Process crypto payment System.out.println("₿ Processing " + currency + " payment of $" + amount); System.out.println(" Wallet: " + walletAddress.substring(0, 10) + "..."); // Generate crypto invoice... return true;
} else { throw new IllegalArgumentException("Unknown payment method: " + paymentMethod); }
// Problems: // - Adding new payment method requires modifying this class // - Each payment method has different validation logic // - Hard to test individual payment methods // - Violates Single Responsibility Principle }}
// Usagepublic class Main { public static void main(String[] args) { PaymentProcessor processor = new PaymentProcessor(); processor.setPaymentMethod("credit_card"); processor.processPayment(99.99, Map.of( "card_number", "1234567890123456", "cvv", "123", "expiry", "12/25" )); }}// ❌ Without Strategy Pattern - if/else nightmare!
class PaymentProcessor { private paymentMethod: string = "credit_card";
setPaymentMethod(method: string): void { this.paymentMethod = method; }
processPayment(amount: number, details: Record<string, string>): boolean { // Problem: Massive if/else chain! if (this.paymentMethod === "credit_card") { const cardNumber = details.card_number; const cvv = details.cvv; const expiry = details.expiry;
// Validate card if (!cardNumber || cardNumber.length !== 16) { throw new Error("Invalid card number"); } if (!cvv || cvv.length !== 3) { throw new Error("Invalid CVV"); }
// Process credit card payment console.log(`💳 Processing credit card payment of $${amount}`); console.log(` Card: **** **** **** ${cardNumber.slice(-4)}`); // Connect to payment gateway... return true;
} else if (this.paymentMethod === "paypal") { const email = details.email;
// Validate PayPal if (!email || !email.includes("@")) { throw new Error("Invalid PayPal email"); }
// Process PayPal payment console.log(`🅿️ Processing PayPal payment of $${amount}`); console.log(` Email: ${email}`); // Redirect to PayPal... return true;
} else if (this.paymentMethod === "crypto") { const walletAddress = details.wallet_address; const currency = details.currency || "BTC";
// Validate crypto if (!walletAddress || walletAddress.length < 26) { throw new Error("Invalid wallet address"); }
// Process crypto payment console.log(`₿ Processing ${currency} payment of $${amount}`); console.log(` Wallet: ${walletAddress.substring(0, 10)}...`); // Generate crypto invoice... return true;
} else { throw new Error(`Unknown payment method: ${this.paymentMethod}`); }
// Problems: // - Adding new payment method requires modifying this class // - Each payment method has different validation logic // - Hard to test individual payment methods // - Violates Single Responsibility Principle }}
// Usageconst processor = new PaymentProcessor();processor.setPaymentMethod("credit_card");processor.processPayment(99.99, { card_number: "1234567890123456", cvv: "123", expiry: "12/25" });// ❌ Without Strategy Pattern - if/else nightmare!
#include <iostream>#include <string>#include <map>#include <stdexcept>
class PaymentProcessor {private: std::string paymentMethod = "credit_card";
public: void setPaymentMethod(const std::string& method) { paymentMethod = method; }
bool processPayment(double amount, const std::map<std::string, std::string>& details) { // Problem: Massive if/else chain! if (paymentMethod == "credit_card") { std::string cardNumber = details.at("card_number"); std::string cvv = details.at("cvv"); std::string expiry = details.at("expiry");
// Validate card if (cardNumber.empty() || cardNumber.length() != 16) { throw std::invalid_argument("Invalid card number"); } if (cvv.empty() || cvv.length() != 3) { throw std::invalid_argument("Invalid CVV"); }
// Process credit card payment std::cout << "💳 Processing credit card payment of $" << amount << std::endl; std::cout << " Card: **** **** **** " << cardNumber.substr(12) << std::endl; // Connect to payment gateway... return true;
} else if (paymentMethod == "paypal") { std::string email = details.at("email");
// Validate PayPal if (email.empty() || email.find("@") == std::string::npos) { throw std::invalid_argument("Invalid PayPal email"); }
// Process PayPal payment std::cout << "🅿️ Processing PayPal payment of $" << amount << std::endl; std::cout << " Email: " << email << std::endl; // Redirect to PayPal... return true;
} else if (paymentMethod == "crypto") { std::string walletAddress = details.at("wallet_address"); std::string currency = details.count("currency") ? details.at("currency") : "BTC";
// Validate crypto if (walletAddress.empty() || walletAddress.length() < 26) { throw std::invalid_argument("Invalid wallet address"); }
// Process crypto payment std::cout << "₿ Processing " << currency << " payment of $" << amount << std::endl; std::cout << " Wallet: " << walletAddress.substr(0, 10) << "..." << std::endl; // Generate crypto invoice... return true;
} else { throw std::invalid_argument("Unknown payment method: " + paymentMethod); }
// Problems: // - Adding new payment method requires modifying this class // - Each payment method has different validation logic // - Hard to test individual payment methods // - Violates Single Responsibility Principle }};
// Usageint main() { PaymentProcessor processor; processor.setPaymentMethod("credit_card"); processor.processPayment(99.99, { {"card_number", "1234567890123456"}, {"cvv", "123"}, {"expiry", "12/25"} }); return 0;}// ❌ Without Strategy Pattern - if/else nightmare!
using System;using System.Collections.Generic;
public class PaymentProcessor{ private string paymentMethod = "credit_card";
public void SetPaymentMethod(string method) { paymentMethod = method; }
public bool ProcessPayment(double amount, Dictionary<string, string> details) { // Problem: Massive if/else chain! if (paymentMethod == "credit_card") { string cardNumber = details["card_number"]; string cvv = details["cvv"]; string expiry = details["expiry"];
// Validate card if (string.IsNullOrEmpty(cardNumber) || cardNumber.Length != 16) { throw new ArgumentException("Invalid card number"); } if (string.IsNullOrEmpty(cvv) || cvv.Length != 3) { throw new ArgumentException("Invalid CVV"); }
// Process credit card payment Console.WriteLine($"💳 Processing credit card payment of ${amount}"); Console.WriteLine($" Card: **** **** **** {cardNumber.Substring(12)}"); // Connect to payment gateway... return true;
} else if (paymentMethod == "paypal") { string email = details["email"];
// Validate PayPal if (string.IsNullOrEmpty(email) || !email.Contains("@")) { throw new ArgumentException("Invalid PayPal email"); }
// Process PayPal payment Console.WriteLine($"🅿️ Processing PayPal payment of ${amount}"); Console.WriteLine($" Email: {email}"); // Redirect to PayPal... return true;
} else if (paymentMethod == "crypto") { string walletAddress = details["wallet_address"]; string currency = details.ContainsKey("currency") ? details["currency"] : "BTC";
// Validate crypto if (string.IsNullOrEmpty(walletAddress) || walletAddress.Length < 26) { throw new ArgumentException("Invalid wallet address"); }
// Process crypto payment Console.WriteLine($"₿ Processing {currency} payment of ${amount}"); Console.WriteLine($" Wallet: {walletAddress.Substring(0, 10)}..."); // Generate crypto invoice... return true;
} else { throw new ArgumentException($"Unknown payment method: {paymentMethod}"); }
// Problems: // - Adding new payment method requires modifying this class // - Each payment method has different validation logic // - Hard to test individual payment methods // - Violates Single Responsibility Principle }}
// Usageclass Program{ static void Main() { PaymentProcessor processor = new PaymentProcessor(); processor.SetPaymentMethod("credit_card"); processor.ProcessPayment(99.99, new Dictionary<string, string> { ["card_number"] = "1234567890123456", ["cvv"] = "123", ["expiry"] = "12/25" }); }}package main
import ( "fmt")
// ❌ Without Strategy Pattern - if/else nightmare!type PaymentProcessor struct { paymentMethod string}
func (p *PaymentProcessor) SetPaymentMethod(method string) { p.paymentMethod = method }
func (p *PaymentProcessor) ProcessPayment(amount float64, details map[string]string) bool { if p.paymentMethod == "credit_card" { fmt.Printf("💳 Processing credit card payment of $%.2f\n", amount) return true } else if p.paymentMethod == "paypal" { fmt.Printf("🅿️ Processing PayPal payment of $%.2f\n", amount) return true } else { panic("Unknown payment method: " + p.paymentMethod) } // Problem: Adding new method requires modifying this class!}
func main() { p := &PaymentProcessor{} p.SetPaymentMethod("credit_card") p.ProcessPayment(99.99, map[string]string{"card_number": "1234567890123456"})}// The Problemfn shipping_cost(kind: &str, weight: f64) -> f64 { if kind == "standard" { weight * 5.0 } else if kind == "express" { weight * 12.0 } else { 0.0 }}Problems:
- Adding new payment methods requires modifying the processor class
- Different validation logic mixed in one class
- Hard to test individual payment methods
- Violates Single Responsibility and Open/Closed principles
The Solution: Strategy Pattern
Section titled “The Solution: Strategy Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodfrom dataclasses import dataclassfrom typing import Dict, Anyfrom enum import Enum
# Step 1: Define result classesclass PaymentStatus(Enum): SUCCESS = "success" FAILED = "failed" PENDING = "pending"
@dataclassclass PaymentResult: """Result of a payment operation""" status: PaymentStatus transaction_id: str message: str
# Step 2: Define the Strategy interfaceclass PaymentStrategy(ABC): """Strategy interface for payment methods"""
@abstractmethod def validate(self, details: Dict[str, Any]) -> bool: """Validate payment details""" pass
@abstractmethod def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult: """Process the payment""" pass
# Step 3: Implement Concrete Strategiesclass CreditCardStrategy(PaymentStrategy): """Credit card payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool: card_number = details.get("card_number", "") cvv = details.get("cvv", "") expiry = details.get("expiry", "")
if len(card_number) != 16 or not card_number.isdigit(): raise ValueError("Invalid card number - must be 16 digits") if len(cvv) != 3 or not cvv.isdigit(): raise ValueError("Invalid CVV - must be 3 digits") if not expiry or "/" not in expiry: raise ValueError("Invalid expiry date - use MM/YY format")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult: self.validate(details)
card_number = details["card_number"] print(f"💳 Processing credit card payment") print(f" Amount: ${amount:.2f}") print(f" Card: **** **** **** {card_number[-4:]}") print(f" Connecting to payment gateway...")
# Simulate payment processing transaction_id = f"CC-{card_number[-4:]}-{int(amount * 100)}"
return PaymentResult( status=PaymentStatus.SUCCESS, transaction_id=transaction_id, message="Credit card payment successful" )
class PayPalStrategy(PaymentStrategy): """PayPal payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool: email = details.get("email", "")
if not email or "@" not in email: raise ValueError("Invalid PayPal email address")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult: self.validate(details)
email = details["email"] print(f"🅿️ Processing PayPal payment") print(f" Amount: ${amount:.2f}") print(f" Email: {email}") print(f" Redirecting to PayPal...")
# Simulate payment processing transaction_id = f"PP-{email.split('@')[0]}-{int(amount * 100)}"
return PaymentResult( status=PaymentStatus.SUCCESS, transaction_id=transaction_id, message="PayPal payment successful" )
class CryptoStrategy(PaymentStrategy): """Cryptocurrency payment strategy"""
def validate(self, details: Dict[str, Any]) -> bool: wallet_address = details.get("wallet_address", "")
if len(wallet_address) < 26: raise ValueError("Invalid wallet address - too short")
return True
def process(self, amount: float, details: Dict[str, Any]) -> PaymentResult: self.validate(details)
wallet_address = details["wallet_address"] currency = details.get("currency", "BTC") print(f"₿ Processing {currency} payment") print(f" Amount: ${amount:.2f}") print(f" Wallet: {wallet_address[:10]}...{wallet_address[-4:]}") print(f" Generating invoice...")
# Simulate payment processing transaction_id = f"CRYPTO-{currency}-{int(amount * 100)}"
return PaymentResult( status=PaymentStatus.PENDING, transaction_id=transaction_id, message=f"Awaiting {currency} confirmation" )
# Step 4: Create the Context classclass PaymentProcessor: """Context class that uses payment strategies"""
def __init__(self, strategy: PaymentStrategy = None): self._strategy = strategy or CreditCardStrategy() # Default
def set_strategy(self, strategy: PaymentStrategy) -> None: """Change payment strategy at runtime""" self._strategy = strategy print(f"\n✅ Payment method changed to: {strategy.__class__.__name__}")
def process_payment(self, amount: float, details: Dict[str, Any]) -> PaymentResult: """Process payment using current strategy""" print(f"\n{'='*50}") print(f"Processing payment of ${amount:.2f}") print(f"{'='*50}")
result = self._strategy.process(amount, details)
print(f"\n📋 Result: {result.status.value}") print(f"📋 Transaction ID: {result.transaction_id}") print(f"📋 Message: {result.message}")
return result
# Step 5: Use the patterndef main(): # Create payment processor processor = PaymentProcessor()
# Process credit card payment processor.process_payment(99.99, { "card_number": "4532015112830366", "cvv": "123", "expiry": "12/25" })
# Switch to PayPal processor.set_strategy(PayPalStrategy()) processor.process_payment(49.99, { })
# Switch to Crypto processor.set_strategy(CryptoStrategy()) processor.process_payment(199.99, { "wallet_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "currency": "BTC" })
# Easy to add new payment method - just create new strategy! print("\n\n✅ Strategy Pattern: Payment methods swapped at runtime!") print("✅ Adding new payment method = Just create a new strategy class!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define result classesenum PaymentStatus { SUCCESS, FAILED, PENDING}
class PaymentResult { /** * Result of a payment operation */ private PaymentStatus status; private String transactionId; private String message;
public PaymentResult(PaymentStatus status, String transactionId, String message) { this.status = status; this.transactionId = transactionId; this.message = message; }
public PaymentStatus getStatus() { return status; } public String getTransactionId() { return transactionId; } public String getMessage() { return message; }}
// Step 2: Define the Strategy interfaceinterface PaymentStrategy { /** * Strategy interface for payment methods */ boolean validate(Map<String, String> details); PaymentResult process(double amount, Map<String, String> details);}
// Step 3: Implement Concrete Strategiesclass CreditCardStrategy implements PaymentStrategy { /** * Credit card payment strategy */ @Override public boolean validate(Map<String, String> details) { String cardNumber = details.getOrDefault("card_number", ""); String cvv = details.getOrDefault("cvv", ""); String expiry = details.getOrDefault("expiry", "");
if (cardNumber.length() != 16 || !cardNumber.matches("\\d+")) { throw new IllegalArgumentException("Invalid card number - must be 16 digits"); } if (cvv.length() != 3 || !cvv.matches("\\d+")) { throw new IllegalArgumentException("Invalid CVV - must be 3 digits"); } if (expiry.isEmpty() || !expiry.contains("/")) { throw new IllegalArgumentException("Invalid expiry date - use MM/YY format"); }
return true; }
@Override public PaymentResult process(double amount, Map<String, String> details) { validate(details);
String cardNumber = details.get("card_number"); System.out.println("💳 Processing credit card payment"); System.out.printf(" Amount: $%.2f%n", amount); System.out.println(" Card: **** **** **** " + cardNumber.substring(12)); System.out.println(" Connecting to payment gateway...");
String transactionId = String.format("CC-%s-%d", cardNumber.substring(12), (int)(amount * 100));
return new PaymentResult( PaymentStatus.SUCCESS, transactionId, "Credit card payment successful" ); }}
class PayPalStrategy implements PaymentStrategy { /** * PayPal payment strategy */ @Override public boolean validate(Map<String, String> details) { String email = details.getOrDefault("email", "");
if (email.isEmpty() || !email.contains("@")) { throw new IllegalArgumentException("Invalid PayPal email address"); }
return true; }
@Override public PaymentResult process(double amount, Map<String, String> details) { validate(details);
String email = details.get("email"); System.out.println("🅿️ Processing PayPal payment"); System.out.printf(" Amount: $%.2f%n", amount); System.out.println(" Email: " + email); System.out.println(" Redirecting to PayPal...");
String transactionId = String.format("PP-%s-%d", email.split("@")[0], (int)(amount * 100));
return new PaymentResult( PaymentStatus.SUCCESS, transactionId, "PayPal payment successful" ); }}
class CryptoStrategy implements PaymentStrategy { /** * Cryptocurrency payment strategy */ @Override public boolean validate(Map<String, String> details) { String walletAddress = details.getOrDefault("wallet_address", "");
if (walletAddress.length() < 26) { throw new IllegalArgumentException("Invalid wallet address - too short"); }
return true; }
@Override public PaymentResult process(double amount, Map<String, String> details) { validate(details);
String walletAddress = details.get("wallet_address"); String currency = details.getOrDefault("currency", "BTC"); System.out.println("₿ Processing " + currency + " payment"); System.out.printf(" Amount: $%.2f%n", amount); System.out.println(" Wallet: " + walletAddress.substring(0, 10) + "..." + walletAddress.substring(walletAddress.length() - 4)); System.out.println(" Generating invoice...");
String transactionId = String.format("CRYPTO-%s-%d", currency, (int)(amount * 100));
return new PaymentResult( PaymentStatus.PENDING, transactionId, "Awaiting " + currency + " confirmation" ); }}
// Step 4: Create the Context classclass PaymentProcessor { /** * Context class that uses payment strategies */ private PaymentStrategy strategy;
public PaymentProcessor() { this.strategy = new CreditCardStrategy(); // Default }
public PaymentProcessor(PaymentStrategy strategy) { this.strategy = strategy; }
public void setStrategy(PaymentStrategy strategy) { // Change payment strategy at runtime this.strategy = strategy; System.out.println("\n✅ Payment method changed to: " + strategy.getClass().getSimpleName()); }
public PaymentResult processPayment(double amount, Map<String, String> details) { // Process payment using current strategy System.out.println("\n" + "=".repeat(50)); System.out.printf("Processing payment of $%.2f%n", amount); System.out.println("=".repeat(50));
PaymentResult result = strategy.process(amount, details);
System.out.println("\n📋 Result: " + result.getStatus()); System.out.println("📋 Transaction ID: " + result.getTransactionId()); System.out.println("📋 Message: " + result.getMessage());
return result; }}
// Step 5: Use the patternpublic class Main { public static void main(String[] args) { // Create payment processor PaymentProcessor processor = new PaymentProcessor();
// Process credit card payment processor.processPayment(99.99, Map.of( "card_number", "4532015112830366", "cvv", "123", "expiry", "12/25" ));
// Switch to PayPal processor.setStrategy(new PayPalStrategy()); processor.processPayment(49.99, Map.of( ));
// Switch to Crypto processor.setStrategy(new CryptoStrategy()); processor.processPayment(199.99, Map.of( "wallet_address", "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "currency", "BTC" ));
// Easy to add new payment method - just create new strategy! System.out.println("\n\n✅ Strategy Pattern: Payment methods swapped at runtime!"); System.out.println("✅ Adding new payment method = Just create a new strategy class!"); }}// Note: TypeScript implementation follows the same structure as Python/Java// with PaymentStatus enum, PaymentResult class, PaymentStrategy interface,// and three concrete strategies (CreditCard, PayPal, Crypto).// Complete implementation available - showing key structure for brevity.
enum PaymentStatus { SUCCESS = "success", FAILED = "failed", PENDING = "pending"}
class PaymentResult { constructor( public status: PaymentStatus, public transactionId: string, public message: string ) {}}
interface PaymentStrategy { validate(details: Record<string, string>): boolean; process(amount: number, details: Record<string, string>): PaymentResult;}
class CreditCardStrategy implements PaymentStrategy { validate(details: Record<string, string>): boolean { // Validation logic return true; }
process(amount: number, details: Record<string, string>): PaymentResult { console.log(`💳 Processing credit card payment of $${amount}`); return new PaymentResult( PaymentStatus.SUCCESS, `CC-${Math.floor(amount * 100)}`, "Payment successful" ); }}
class PayPalStrategy implements PaymentStrategy { validate(details: Record<string, string>): boolean { return true; }
process(amount: number, details: Record<string, string>): PaymentResult { console.log(`🅿️ Processing PayPal payment of $${amount}`); return new PaymentResult( PaymentStatus.PENDING, `PP-${Math.floor(amount * 100)}`, "Redirecting to PayPal..." ); }}
class CryptoStrategy implements PaymentStrategy { validate(details: Record<string, string>): boolean { return true; }
process(amount: number, details: Record<string, string>): PaymentResult { const currency = details.currency || "BTC"; console.log(`₿ Processing ${currency} payment of $${amount}`); return new PaymentResult( PaymentStatus.PENDING, `CRYPTO-${currency}-${Math.floor(amount * 100)}`, `Awaiting ${currency} confirmation` ); }}
class PaymentProcessor { private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy = new CreditCardStrategy()) { this.strategy = strategy; }
setStrategy(strategy: PaymentStrategy): void { this.strategy = strategy; console.log(`\n✅ Payment method changed to: ${strategy.constructor.name}`); }
processPayment(amount: number, details: Record<string, string>): PaymentResult { console.log(`\n${"=".repeat(50)}`); console.log(`Processing payment of $${amount}`); console.log("=".repeat(50));
const result = this.strategy.process(amount, details); console.log(`\n📋 Result: ${result.status}`); console.log(`📋 Transaction ID: ${result.transactionId}`);
return result; }}
// Usageconst processor = new PaymentProcessor();processor.processPayment(99.99, { card_number: "4532015112830366", cvv: "123", expiry: "12/25" });processor.setStrategy(new PayPalStrategy());processor.setStrategy(new CryptoStrategy());processor.processPayment(199.99, { wallet_address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", currency: "BTC" });console.log("\n\n✅ Strategy Pattern: Payment methods swapped at runtime!");// Note: C++ implementation follows the same structure// Complete code showing key components for brevity
#include <iostream>#include <string>#include <map>#include <memory>
enum class PaymentStatus { SUCCESS, FAILED, PENDING};
struct PaymentResult { PaymentStatus status; std::string transactionId; std::string message;};
class PaymentStrategy {public: virtual ~PaymentStrategy() = default; virtual bool validate(const std::map<std::string, std::string>& details) = 0; virtual PaymentResult process(double amount, const std::map<std::string, std::string>& details) = 0;};
class CreditCardStrategy : public PaymentStrategy {public: bool validate(const std::map<std::string, std::string>& details) override { return true; // Simplified }
PaymentResult process(double amount, const std::map<std::string, std::string>& details) override { std::cout << "💳 Processing credit card payment of $" << amount << std::endl; return {PaymentStatus::SUCCESS, "CC-" + std::to_string((int)(amount * 100)), "Payment successful"}; }};
class PayPalStrategy : public PaymentStrategy {public: bool validate(const std::map<std::string, std::string>& details) override { return true; }
PaymentResult process(double amount, const std::map<std::string, std::string>& details) override { std::cout << "🅿️ Processing PayPal payment of $" << amount << std::endl; return {PaymentStatus::PENDING, "PP-" + std::to_string((int)(amount * 100)), "Redirecting to PayPal..."}; }};
class CryptoStrategy : public PaymentStrategy {public: bool validate(const std::map<std::string, std::string>& details) override { return true; }
PaymentResult process(double amount, const std::map<std::string, std::string>& details) override { std::string currency = details.count("currency") ? details.at("currency") : "BTC"; std::cout << "₿ Processing " << currency << " payment of $" << amount << std::endl; return {PaymentStatus::PENDING, "CRYPTO-" + currency, "Awaiting confirmation"}; }};
class PaymentProcessor {private: std::unique_ptr<PaymentStrategy> strategy;
public: PaymentProcessor() : strategy(std::make_unique<CreditCardStrategy>()) {}
void setStrategy(std::unique_ptr<PaymentStrategy> newStrategy) { strategy = std::move(newStrategy); std::cout << "\n✅ Payment method changed" << std::endl; }
PaymentResult processPayment(double amount, const std::map<std::string, std::string>& details) { std::cout << "\n" << std::string(50, '=') << std::endl; std::cout << "Processing payment of $" << amount << std::endl; std::cout << std::string(50, '=') << std::endl;
auto result = strategy->process(amount, details); std::cout << "\n📋 Transaction ID: " << result.transactionId << std::endl;
return result; }};
// Usage example shown in comments for brevity// Note: C# implementation follows the same structure// Complete code showing key components for brevity
using System;using System.Collections.Generic;
public enum PaymentStatus{ SUCCESS, FAILED, PENDING}
public class PaymentResult{ public PaymentStatus Status { get; set; } public string TransactionId { get; set; } public string Message { get; set; }
public PaymentResult(PaymentStatus status, string transactionId, string message) { Status = status; TransactionId = transactionId; Message = message; }}
public interface IPaymentStrategy{ bool Validate(Dictionary<string, string> details); PaymentResult Process(double amount, Dictionary<string, string> details);}
public class CreditCardStrategy : IPaymentStrategy{ public bool Validate(Dictionary<string, string> details) { return true; // Simplified }
public PaymentResult Process(double amount, Dictionary<string, string> details) { Console.WriteLine($"💳 Processing credit card payment of ${amount}"); return new PaymentResult( PaymentStatus.SUCCESS, $"CC-{(int)(amount * 100)}", "Payment successful" ); }}
public class PayPalStrategy : IPaymentStrategy{ public bool Validate(Dictionary<string, string> details) { return true; }
public PaymentResult Process(double amount, Dictionary<string, string> details) { Console.WriteLine($"🅿️ Processing PayPal payment of ${amount}"); return new PaymentResult( PaymentStatus.PENDING, $"PP-{(int)(amount * 100)}", "Redirecting to PayPal..." ); }}
public class CryptoStrategy : IPaymentStrategy{ public bool Validate(Dictionary<string, string> details) { return true; }
public PaymentResult Process(double amount, Dictionary<string, string> details) { string currency = details.ContainsKey("currency") ? details["currency"] : "BTC"; Console.WriteLine($"₿ Processing {currency} payment of ${amount}"); return new PaymentResult( PaymentStatus.PENDING, $"CRYPTO-{currency}-{(int)(amount * 100)}", $"Awaiting {currency} confirmation" ); }}
public class PaymentProcessor{ private IPaymentStrategy strategy;
public PaymentProcessor() : this(new CreditCardStrategy()) { }
public PaymentProcessor(IPaymentStrategy strategy) { this.strategy = strategy; }
public void SetStrategy(IPaymentStrategy strategy) { this.strategy = strategy; Console.WriteLine($"\n✅ Payment method changed to: {strategy.GetType().Name}"); }
public PaymentResult ProcessPayment(double amount, Dictionary<string, string> details) { Console.WriteLine($"\n{new string('=', 50)}"); Console.WriteLine($"Processing payment of ${amount}"); Console.WriteLine(new string('=', 50));
var result = strategy.Process(amount, details); Console.WriteLine($"\n📋 Result: {result.Status}"); Console.WriteLine($"📋 Transaction ID: {result.TransactionId}");
return result; }}
// Usageclass Program{ static void Main() { var processor = new PaymentProcessor(); processor.ProcessPayment(99.99, new Dictionary<string, string> { ["card_number"] = "4532015112830366", ["cvv"] = "123", ["expiry"] = "12/25" }); processor.SetStrategy(new PayPalStrategy()); processor.ProcessPayment(49.99, new Dictionary<string, string> { ["email"] = "[email protected]" }); processor.SetStrategy(new CryptoStrategy()); processor.ProcessPayment(199.99, new Dictionary<string, string> { ["wallet_address"] = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", ["currency"] = "BTC" }); Console.WriteLine("\n\n✅ Strategy Pattern: Payment methods swapped at runtime!"); }}package main
import "fmt"
type PaymentResult struct { Status string TransactionID string Message string}
// Strategy interfacetype PaymentStrategy interface { Process(amount float64, details map[string]string) PaymentResult}
// Concrete strategiestype CreditCardStrategy struct{}
func (c *CreditCardStrategy) Process(amount float64, details map[string]string) PaymentResult { fmt.Printf("💳 Processing credit card payment of $%.2f\n", amount) return PaymentResult{"SUCCESS", fmt.Sprintf("CC-%d", int(amount*100)), "Payment successful"}}
type PayPalStrategy struct{}
func (p *PayPalStrategy) Process(amount float64, details map[string]string) PaymentResult { fmt.Printf("🅿️ Processing PayPal payment of $%.2f\n", amount) return PaymentResult{"PENDING", fmt.Sprintf("PP-%d", int(amount*100)), "Redirecting to PayPal..."}}
// Contexttype PaymentProcessor struct{ strategy PaymentStrategy }
func NewPaymentProcessor(s PaymentStrategy) *PaymentProcessor { return &PaymentProcessor{s} }func (p *PaymentProcessor) SetStrategy(s PaymentStrategy) { p.strategy = s fmt.Printf("\n✅ Payment method changed\n")}func (p *PaymentProcessor) ProcessPayment(amount float64, details map[string]string) PaymentResult { return p.strategy.Process(amount, details)}
func main() { processor := NewPaymentProcessor(&CreditCardStrategy{}) processor.ProcessPayment(99.99, map[string]string{"card_number": "4532015112830366"}) processor.SetStrategy(&PayPalStrategy{}) fmt.Println("\n✅ Strategy Pattern: Payment methods swapped at runtime!")}// Class Structuretrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}Strategy Pattern Variants
Section titled “Strategy Pattern Variants”There are different ways to implement the Strategy Pattern:
1. Classic Strategy (Class-Based)
Section titled “1. Classic Strategy (Class-Based)”Using abstract classes/interfaces:
# Classic Strategy - class-basedfrom abc import ABC, abstractmethod
class Strategy(ABC): @abstractmethod def execute(self, data): pass
class ConcreteStrategyA(Strategy): def execute(self, data): return f"Strategy A: {data}"
class Context: def __init__(self, strategy: Strategy): self._strategy = strategy
def do_something(self, data): return self._strategy.execute(data)// Classic Strategy - class-basedinterface Strategy { String execute(String data);}
class ConcreteStrategyA implements Strategy { @Override public String execute(String data) { return "Strategy A: " + data; }}
class Context { private Strategy strategy;
public Context(Strategy strategy) { this.strategy = strategy; }
public String doSomething(String data) { return strategy.execute(data); }}// Classic Strategy - class-basedinterface Strategy { execute(data: string): string;}
class ConcreteStrategyA implements Strategy { execute(data: string): string { return `Strategy A: ${data}`; }}
class Context { private strategy: Strategy;
constructor(strategy: Strategy) { this.strategy = strategy; }
doSomething(data: string): string { return this.strategy.execute(data); }}// Classic Strategy - class-based#include <string>
class Strategy {public: virtual ~Strategy() = default; virtual std::string execute(const std::string& data) = 0;};
class ConcreteStrategyA : public Strategy {public: std::string execute(const std::string& data) override { return "Strategy A: " + data; }};
class Context {private: Strategy* strategy;
public: Context(Strategy* strategy) : strategy(strategy) {}
std::string doSomething(const std::string& data) { return strategy->execute(data); }};// Classic Strategy - class-basedpublic interface IStrategy{ string Execute(string data);}
public class ConcreteStrategyA : IStrategy{ public string Execute(string data) { return $"Strategy A: {data}"; }}
public class Context{ private IStrategy strategy;
public Context(IStrategy strategy) { this.strategy = strategy; }
public string DoSomething(string data) { return strategy.Execute(data); }}// Classic Strategy - interface-basedtype Strategy interface { Execute(data string) string}
type ConcreteStrategyA struct{}
func (a *ConcreteStrategyA) Execute(data string) string { return "Strategy A: " + data}
type Context struct{ strategy Strategy }
func NewContext(s Strategy) *Context { return &Context{s} }func (c *Context) DoSomething(data string) string { return c.strategy.Execute(data) }// 1. Classic Strategy Class-Basedtrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}Pros: Type-safe, clear contracts, easy to extend
Cons: More classes to manage
2. Functional Strategy (Lambda-Based)
Section titled “2. Functional Strategy (Lambda-Based)”Using functions/lambdas:
# Functional Strategy - using functionsfrom typing import Callable
class Context: def __init__(self, strategy: Callable): self._strategy = strategy
def do_something(self, data): return self._strategy(data)
# Use lambda or function as strategycontext = Context(lambda data: f"Strategy A: {data}")print(context.do_something("Hello"))
# Change strategycontext._strategy = lambda data: f"Strategy B: {data.upper()}"print(context.do_something("Hello"))// Functional Strategy - using lambdasimport java.util.function.Function;
class Context { private Function<String, String> strategy;
public Context(Function<String, String> strategy) { this.strategy = strategy; }
public void setStrategy(Function<String, String> strategy) { this.strategy = strategy; }
public String doSomething(String data) { return strategy.apply(data); }}
// Use lambda as strategypublic class Main { public static void main(String[] args) { Context context = new Context(data -> "Strategy A: " + data); System.out.println(context.doSomething("Hello"));
// Change strategy context.setStrategy(data -> "Strategy B: " + data.toUpperCase()); System.out.println(context.doSomething("Hello")); }}// Functional Strategy - using functionstype Strategy = (data: string) => string;
class Context { private strategy: Strategy;
constructor(strategy: Strategy) { this.strategy = strategy; }
doSomething(data: string): string { return this.strategy(data); }}
// Use arrow function as strategylet context = new Context((data) => `Strategy A: ${data}`);console.log(context.doSomething("Hello"));
// Change strategycontext = new Context((data) => `Strategy B: ${data.toUpperCase()}`);console.log(context.doSomething("Hello"));// Functional Strategy - using functionsusing Strategy = std::function<std::string(const std::string&)>;
class Context {private: Strategy strategy;
public: Context(Strategy strategy) : strategy(strategy) {}
std::string doSomething(const std::string& data) { return strategy(data); }};
// Use lambda as strategy// Context context([](const std::string& data) { return "Strategy A: " + data; });// context.doSomething("Hello");// Functional Strategy - using lambdasusing System;
public class Context{ private Func<string, string> strategy;
public Context(Func<string, string> strategy) { this.strategy = strategy; }
public string DoSomething(string data) { return strategy(data); }}
// Usageclass Program{ static void Main() { // Use lambda as strategy var context = new Context(data => $"Strategy A: {data}"); Console.WriteLine(context.DoSomething("Hello"));
// Change strategy context = new Context(data => $"Strategy B: {data.ToUpper()}"); Console.WriteLine(context.DoSomething("Hello")); }}// Functional Strategy - using function valuestype FuncContext struct { strategy func(string) string}
func (c *FuncContext) DoSomething(data string) string { return c.strategy(data) }
func main() { ctx := &FuncContext{strategy: func(data string) string { return "Strategy A: " + data }} fmt.Println(ctx.DoSomething("Hello"))
// Change strategy ctx.strategy = func(data string) string { return "Strategy B: " + strings.ToUpper(data) } fmt.Println(ctx.DoSomething("Hello"))}// 2. Functional Strategy Lambda-Basedtrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}Pros: Less boilerplate, more flexible
Cons: Less type-safe, harder to document
3. Strategy with Configuration
Section titled “3. Strategy with Configuration”Strategies that can be configured:
# Strategy with configurationclass CompressionStrategy(ABC): @abstractmethod def compress(self, data: bytes) -> bytes: pass
class GzipStrategy(CompressionStrategy): def __init__(self, level: int = 6): self.level = level # Compression level 1-9
def compress(self, data: bytes) -> bytes: import gzip return gzip.compress(data, compresslevel=self.level)
# Usage - strategy with different configurationsfast_compression = GzipStrategy(level=1)max_compression = GzipStrategy(level=9)// Strategy with configurationinterface CompressionStrategy { byte[] compress(byte[] data);}
class GzipStrategy implements CompressionStrategy { private int level; // Compression level 1-9
public GzipStrategy(int level) { this.level = level; }
@Override public byte[] compress(byte[] data) { // Use compression level... return data; // Simplified }}
// Usage - strategy with different configurationsCompressionStrategy fastCompression = new GzipStrategy(1);CompressionStrategy maxCompression = new GzipStrategy(9);// Strategy with configurationabstract class CompressionStrategy { abstract compress(data: Buffer): Buffer;}
class GzipStrategy extends CompressionStrategy { private level: number; // Compression level 1-9
constructor(level: number = 6) { super(); this.level = level; }
compress(data: Buffer): Buffer { // Use compression level... return data; // Simplified }}
// Usage - strategy with different configurationsconst fastCompression = new GzipStrategy(1);const maxCompression = new GzipStrategy(9);// Strategy with configuration#include <vector>#include <cstdint>
using Bytes = std::vector<uint8_t>;
class CompressionStrategy {public: virtual ~CompressionStrategy() = default; virtual Bytes compress(const Bytes& data) = 0;};
class GzipStrategy : public CompressionStrategy {private: int level; // Compression level 1-9
public: GzipStrategy(int level = 6) : level(level) {}
Bytes compress(const Bytes& data) override { // Use compression level... return data; // Simplified }};
// Usage - strategy with different configurations// GzipStrategy fastCompression(1);// GzipStrategy maxCompression(9);// Strategy with configurationusing System;
public interface ICompressionStrategy{ byte[] Compress(byte[] data);}
public class GzipStrategy : ICompressionStrategy{ private int level; // Compression level 1-9
public GzipStrategy(int level = 6) { this.level = level; }
public byte[] Compress(byte[] data) { // Use compression level... return data; // Simplified }}
// Usage - strategy with different configurationsclass Program{ static void Main() { ICompressionStrategy fastCompression = new GzipStrategy(1); ICompressionStrategy maxCompression = new GzipStrategy(9); }}// Strategy with configurationtype CompressionStrategy interface { Compress(data []byte) []byte}
type GzipStrategy struct { level int // Compression level 1-9}
func NewGzipStrategy(level int) *GzipStrategy { return &GzipStrategy{level: level} }
func (g *GzipStrategy) Compress(data []byte) []byte { // Use compression level g.level... return data // Simplified}
// Usage - strategy with different configurations// fastCompression := NewGzipStrategy(1)// maxCompression := NewGzipStrategy(9)// 3. Strategy with Configurationtrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}When to Use Strategy Pattern?
Section titled “When to Use Strategy Pattern?”Use Strategy Pattern when:
✅ You have multiple algorithms - Different ways to do the same thing
✅ You need runtime switching - Change algorithm based on user input or conditions
✅ You want to eliminate conditionals - Replace if/else or switch statements
✅ Algorithms should be interchangeable - Same interface, different implementations
✅ You need to isolate algorithm code - Each algorithm in its own class
When NOT to Use Strategy Pattern?
Section titled “When NOT to Use Strategy Pattern?”Don’t use Strategy Pattern when:
❌ Only one algorithm exists - No need to abstract
❌ Algorithm never changes - Static behavior is simpler
❌ Simple conditionals - 2-3 branches might be clearer without pattern
❌ Performance is critical - Indirection has small overhead
❌ Over-engineering - Don’t add complexity for hypothetical future needs
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Strategies That Share State
Section titled “Mistake 1: Strategies That Share State”# ❌ Bad: Strategy with shared mutable stateclass BadStrategy: shared_cache = {} # Class-level shared state!
def execute(self, data): self.shared_cache[data] = result # Bad: Mutating shared state return result
# ✅ Good: Strategy without shared stateclass GoodStrategy: def __init__(self): self._cache = {} # Instance-level state
def execute(self, data): if data not in self._cache: self._cache[data] = self._compute(data) return self._cache[data]// ❌ Bad: Strategy with shared mutable stateclass BadStrategy implements Strategy { private static Map<String, Object> sharedCache = new HashMap<>(); // Shared state!
@Override public Object execute(String data) { sharedCache.put(data, result); // Bad: Mutating shared state return result; }}
// ✅ Good: Strategy without shared stateclass GoodStrategy implements Strategy { private Map<String, Object> cache = new HashMap<>(); // Instance-level
@Override public Object execute(String data) { if (!cache.containsKey(data)) { cache.put(data, compute(data)); } return cache.get(data); }}// ❌ Bad: Strategy with shared mutable stateclass BadStrategy { static sharedCache: Record<string, any> = {}; // Class-level shared state!
execute(data: string): any { BadStrategy.sharedCache[data] = "result"; // Bad: Mutating shared state return "result"; }}
// ✅ Good: Strategy without shared stateclass GoodStrategy { private cache: Record<string, any> = {}; // Instance-level state
execute(data: string): any { if (!(data in this.cache)) { this.cache[data] = this.compute(data); } return this.cache[data]; }
private compute(data: string): any { return `Computed: ${data}`; }}// ❌ Bad: Strategy with shared mutable stateclass BadStrategy {public: static std::map<std::string, std::string> sharedCache; // Shared state!
std::string execute(const std::string& data) { sharedCache[data] = "result"; // Bad: Mutating shared state return "result"; }};
std::map<std::string, std::string> BadStrategy::sharedCache;
// ✅ Good: Strategy without shared stateclass GoodStrategy {private: std::map<std::string, std::string> cache; // Instance-level
public: std::string execute(const std::string& data) { if (cache.find(data) == cache.end()) { cache[data] = compute(data); } return cache[data]; }
private: std::string compute(const std::string& data) { return "Computed: " + data; }};// ❌ Bad: Strategy with shared mutable statepublic class BadStrategy{ private static Dictionary<string, object> sharedCache = new Dictionary<string, object>(); // Shared state!
public object Execute(string data) { sharedCache[data] = "result"; // Bad: Mutating shared state return "result"; }}
// ✅ Good: Strategy without shared statepublic class GoodStrategy{ private Dictionary<string, object> cache = new Dictionary<string, object>(); // Instance-level
public object Execute(string data) { if (!cache.ContainsKey(data)) { cache[data] = Compute(data); } return cache[data]; }
private object Compute(string data) { return $"Computed: {data}"; }}// ❌ Bad: Strategy with shared mutable statevar sharedCache = map[string]any{} // Shared state!
type BadStrategy struct{}
func (b *BadStrategy) Execute(data string) any { sharedCache[data] = "result" // Bad: mutating shared state return "result"}
// ✅ Good: Strategy with instance-level statetype GoodStrategy struct { cache map[string]any}
func NewGoodStrategy() *GoodStrategy { return &GoodStrategy{cache: map[string]any{}} }
func (g *GoodStrategy) Execute(data string) any { if _, ok := g.cache[data]; !ok { g.cache[data] = "Computed: " + data } return g.cache[data]}// Mistake 1: Strategies That Share Statefn shipping_cost(kind: &str, weight: f64) -> f64 { if kind == "standard" { weight * 5.0 } else if kind == "express" { weight * 12.0 } else { 0.0 }}Mistake 2: Context Exposing Strategy Details
Section titled “Mistake 2: Context Exposing Strategy Details”# ❌ Bad: Context exposes strategy internalsclass BadContext: def __init__(self, strategy): self.strategy = strategy # Public access!
def get_strategy_name(self): # Bad: Exposing strategy details return self.strategy.__class__.__name__
# ✅ Good: Context hides strategy detailsclass GoodContext: def __init__(self, strategy): self._strategy = strategy # Private
def execute(self, data): return self._strategy.execute(data) # No methods exposing strategy internals// ❌ Bad: Context exposes strategy internalsclass BadContext { public Strategy strategy; // Public access!
public String getStrategyName() { // Bad: Exposing strategy details return strategy.getClass().getSimpleName(); }}
// ✅ Good: Context hides strategy detailsclass GoodContext { private Strategy strategy; // Private
public GoodContext(Strategy strategy) { this.strategy = strategy; }
public Object execute(String data) { return strategy.execute(data); } // No methods exposing strategy internals}// ❌ Bad: Context exposes strategy internalsclass BadContext { strategy: any; // Public access!
constructor(strategy: any) { this.strategy = strategy; }
getStrategyName(): string { // Bad: Exposing strategy details return this.strategy.constructor.name; }}
// ✅ Good: Context hides strategy detailsclass GoodContext { private strategy: any; // Private
constructor(strategy: any) { this.strategy = strategy; }
execute(data: string): any { return this.strategy.execute(data); } // No methods exposing strategy internals}// ❌ Bad: Context exposes strategy internalsclass BadContext {public: Strategy* strategy; // Public access!
std::string getStrategyName() { // Bad: Exposing strategy details return typeid(*strategy).name(); }};
// ✅ Good: Context hides strategy detailsclass GoodContext {private: Strategy* strategy; // Private
public: GoodContext(Strategy* strategy) : strategy(strategy) {}
std::string execute(const std::string& data) { return strategy->execute(data); } // No methods exposing strategy internals};// ❌ Bad: Context exposes strategy internalspublic class BadContext{ public IStrategy Strategy; // Public access!
public string GetStrategyName() // Bad: Exposing strategy details { return Strategy.GetType().Name; }}
// ✅ Good: Context hides strategy detailspublic class GoodContext{ private IStrategy strategy; // Private
public GoodContext(IStrategy strategy) { this.strategy = strategy; }
public object Execute(string data) { return strategy.Execute(data); } // No methods exposing strategy internals}// ❌ Bad: Context exposes strategy internalstype BadContext struct { Strategy StrategyI // Public - exposes strategy!}
// ✅ Good: Context hides strategy detailstype GoodContext struct { strategy StrategyI // unexported - private}
func NewGoodContext(s StrategyI) *GoodContext { return &GoodContext{s} }func (c *GoodContext) Execute(data string) any { return c.strategy.Execute(data) }// No methods exposing strategy internals// Mistake 2: Context Exposing Strategy Detailsfn shipping_cost(kind: &str, weight: f64) -> f64 { if kind == "standard" { weight * 5.0 } else if kind == "express" { weight * 12.0 } else { 0.0 }}Mistake 3: Strategy Knowing About Context
Section titled “Mistake 3: Strategy Knowing About Context”# ❌ Bad: Strategy depends on Contextclass BadStrategy: def __init__(self, context): # Bad: Strategy knows about context self.context = context
def execute(self, data): return self.context.some_method() # Bad: Tight coupling!
# ✅ Good: Strategy is independentclass GoodStrategy: def execute(self, data, helper_func=None): # Strategy doesn't know about context # If needed, pass data through parameters if helper_func: return helper_func(data) return self._process(data)// ❌ Bad: Strategy depends on Contextclass BadStrategy implements Strategy { private Context context; // Bad: Strategy knows about context
public BadStrategy(Context context) { this.context = context; }
@Override public Object execute(String data) { return context.someMethod(); // Bad: Tight coupling! }}
// ✅ Good: Strategy is independentclass GoodStrategy implements Strategy { @Override public Object execute(String data) { // Strategy doesn't know about context return process(data); }}// ❌ Bad: Strategy depends on Contextclass BadStrategy { private context: any; // Bad: Strategy knows about context
constructor(context: any) { this.context = context; }
execute(data: string): any { return this.context.someMethod(); // Bad: Tight coupling! }}
// ✅ Good: Strategy is independentclass GoodStrategy { execute(data: string, helperFunc?: (data: string) => any): any { // Strategy doesn't know about context // If needed, pass data through parameters if (helperFunc) { return helperFunc(data); } return this.process(data); }
private process(data: string): any { return `Processed: ${data}`; }}// ❌ Bad: Strategy depends on Contextclass BadStrategy {private: Context* context; // Bad: Strategy knows about context
public: BadStrategy(Context* context) : context(context) {}
std::string execute(const std::string& data) { return context->someMethod(); // Bad: Tight coupling! }};
// ✅ Good: Strategy is independentclass GoodStrategy {public: std::string execute(const std::string& data) { // Strategy doesn't know about context return process(data); }
private: std::string process(const std::string& data) { return "Processed: " + data; }};// ❌ Bad: Strategy depends on Contextpublic class BadStrategy : IStrategy{ private Context context; // Bad: Strategy knows about context
public BadStrategy(Context context) { this.context = context; }
public object Execute(string data) { return context.SomeMethod(); // Bad: Tight coupling! }}
// ✅ Good: Strategy is independentpublic class GoodStrategy : IStrategy{ public object Execute(string data) { // Strategy doesn't know about context return Process(data); }
private object Process(string data) { return $"Processed: {data}"; }}// ❌ Bad: Strategy depends on Contexttype BadStrategyImpl struct { ctx *ContextImpl // Bad: strategy knows about context}
func (b *BadStrategyImpl) Execute(data string) any { return b.ctx.SomeMethod() // Bad: tight coupling!}
// ✅ Good: Strategy is independenttype GoodStrategyImpl struct{}
func (g *GoodStrategyImpl) Execute(data string) any { // Strategy doesn't know about context return "Processed: " + data}// Mistake 3: Strategy Knowing About Contextfn shipping_cost(kind: &str, weight: f64) -> f64 { if kind == "standard" { weight * 5.0 } else if kind == "express" { weight * 12.0 } else { 0.0 }}Benefits of Strategy Pattern
Section titled “Benefits of Strategy Pattern”- Open/Closed Principle - Add new algorithms without modifying existing code
- Single Responsibility - Each algorithm in its own class
- Runtime Flexibility - Change algorithms dynamically
- Eliminates Conditionals - No more if/else chains
- Easy Testing - Test each strategy independently
- Code Reuse - Strategies can be reused across contexts
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Strategy Pattern?
Section titled “What is Strategy Pattern?”Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
Why Use It?
Section titled “Why Use It?”- ✅ Multiple algorithms - Different ways to accomplish same task
- ✅ Runtime switching - Change algorithm based on conditions
- ✅ Eliminate conditionals - No if/else chains
- ✅ Easy testing - Test each strategy independently
- ✅ Follow Open/Closed Principle
How It Works?
Section titled “How It Works?”- Define Strategy interface - Common interface for all algorithms
- Create Concrete Strategies - Each algorithm in its own class
- Create Context - Uses a strategy through the interface
- Set Strategy - Context can change strategy at runtime
- Execute - Context delegates to current strategy
Key Components
Section titled “Key Components”Context → Strategy Interface → Concrete Strategies- Strategy - Interface for all algorithms
- Concrete Strategy - Specific algorithm implementation
- Context - Uses a strategy, allows switching
- Client - Configures context with strategy
Simple Example
Section titled “Simple Example”from abc import ABC, abstractmethod
class Strategy(ABC): @abstractmethod def execute(self, data): pass
class ConcreteStrategy(Strategy): def execute(self, data): return process(data)
class Context: def __init__(self, strategy: Strategy): self._strategy = strategy def set_strategy(self, strategy: Strategy): self._strategy = strategy def do_work(self, data): return self._strategy.execute(data)interface Strategy { Object execute(Object data); }
class ConcreteStrategy implements Strategy { public Object execute(Object data) { return process(data); }}
class Context { private Strategy strategy; Context(Strategy s) { strategy = s; } void setStrategy(Strategy s) { strategy = s; } Object doWork(Object data) { return strategy.execute(data); }}interface Strategy { execute(data: any): any; }
class ConcreteStrategy implements Strategy { execute(data: any): any { return process(data); }}
class Context { constructor(private strategy: Strategy) {} setStrategy(s: Strategy): void { this.strategy = s; } doWork(data: any): any { return this.strategy.execute(data); }}class Strategy {public: virtual void* execute(void* data) = 0;};
class ConcreteStrategy : public Strategy {public: void* execute(void* data) override { return process(data); }};
class Context { Strategy* strategy;public: Context(Strategy* s) : strategy(s) {} void setStrategy(Strategy* s) { strategy = s; } void* doWork(void* data) { return strategy->execute(data); }};interface IStrategy { object Execute(object data);}
class ConcreteStrategy : IStrategy { public object Execute(object data) => Process(data);}
class Context { private IStrategy strategy; public Context(IStrategy s) => strategy = s; public void SetStrategy(IStrategy s) => strategy = s; public object DoWork(object data) => strategy.Execute(data);}type Strategy interface { Execute(data any) any }type ConcreteStrategy struct{}func (c *ConcreteStrategy) Execute(data any) any { return data }type Context struct{ strategy Strategy }func (c *Context) SetStrategy(s Strategy) { c.strategy = s }func (c *Context) DoWork(data any) any { return c.strategy.Execute(data) }// Simple Exampletrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}When to Use?
Section titled “When to Use?”✅ Multiple algorithms for same task
✅ Need runtime algorithm switching
✅ Growing if/else chain for algorithm selection
✅ Want to test algorithms independently
✅ Algorithms should be interchangeable
When NOT to Use?
Section titled “When NOT to Use?”❌ Only one algorithm
❌ Algorithm never changes
❌ Simple 2-3 branch conditionals
❌ Over-engineering simple problems
Key Takeaways
Section titled “Key Takeaways”- Strategy Pattern = Interchangeable algorithms
- Strategy = Algorithm interface
- Context = Uses strategy, allows switching
- Benefit = Flexibility, testability, no conditionals
- Principle = Open for extension, closed for modification
Common Pattern Structure
Section titled “Common Pattern Structure”from abc import ABC, abstractmethod
# 1. Strategy Interfaceclass Strategy(ABC): @abstractmethod def execute(self, data): pass
# 2. Concrete Strategiesclass StrategyA(Strategy): def execute(self, data): return process_a(data)
class StrategyB(Strategy): def execute(self, data): return process_b(data)
# 3. Contextclass Context: def __init__(self, strategy: Strategy): self._strategy = strategy def set_strategy(self, strategy: Strategy): self._strategy = strategy
def execute(self, data): return self._strategy.execute(data)
# 4. Usagecontext = Context(StrategyA())context.execute(data)context.set_strategy(StrategyB())context.execute(data)// 1. Strategy Interfaceinterface Strategy { Object execute(Object data); }
// 2. Concrete Strategiesclass StrategyA implements Strategy { public Object execute(Object data) { return processA(data); }}class StrategyB implements Strategy { public Object execute(Object data) { return processB(data); }}
// 3. Contextclass Context { private Strategy strategy; Context(Strategy s) { strategy = s; } void setStrategy(Strategy s) { strategy = s; } Object execute(Object data) { return strategy.execute(data); }}
// 4. UsageContext ctx = new Context(new StrategyA());ctx.execute(data);ctx.setStrategy(new StrategyB());ctx.execute(data);// 1. Strategy Interfaceinterface Strategy { execute(data: any): any; }
// 2. Concrete Strategiesclass StrategyA implements Strategy { execute(data: any): any { return processA(data); }}class StrategyB implements Strategy { execute(data: any): any { return processB(data); }}
// 3. Contextclass Context { constructor(private strategy: Strategy) {} setStrategy(s: Strategy): void { this.strategy = s; } execute(data: any): any { return this.strategy.execute(data); }}
// 4. Usageconst ctx = new Context(new StrategyA());ctx.execute(data);ctx.setStrategy(new StrategyB());ctx.execute(data);// 1. Strategy Interfaceclass Strategy { public: virtual void* execute(void* data) = 0; };
// 2. Concrete Strategiesclass StrategyA : public Strategy { void* execute(void* data) override { return processA(data); }};class StrategyB : public Strategy { void* execute(void* data) override { return processB(data); }};
// 3. Contextclass Context { Strategy* strategy;public: Context(Strategy* s) : strategy(s) {} void setStrategy(Strategy* s) { strategy = s; } void* execute(void* data) { return strategy->execute(data); }};
// 4. UsageContext ctx(new StrategyA());ctx.execute(data);ctx.setStrategy(new StrategyB());ctx.execute(data);// 1. Strategy Interfaceinterface IStrategy { object Execute(object data); }
// 2. Concrete Strategiesclass StrategyA : IStrategy { public object Execute(object data) => ProcessA(data);}class StrategyB : IStrategy { public object Execute(object data) => ProcessB(data);}
// 3. Contextclass Context { private IStrategy strategy; public Context(IStrategy s) => strategy = s; public void SetStrategy(IStrategy s) => strategy = s; public object Execute(object data) => strategy.Execute(data);}
// 4. Usagevar ctx = new Context(new StrategyA());ctx.Execute(data);ctx.SetStrategy(new StrategyB());ctx.Execute(data);// 1. Strategy interfacetype Strategy interface { Execute(data any) any }// 2. Concrete strategiestype StratA struct{}func (a *StratA) Execute(data any) any { return data }type StratB struct{}func (b *StratB) Execute(data any) any { return data }// 3. Contexttype Context struct{ strategy Strategy }func (c *Context) SetStrategy(s Strategy) { c.strategy = s }func (c *Context) Execute(data any) any { return c.strategy.Execute(data) }// 4. Usage// ctx := &Context{strategy: &StratA{}}// ctx.Execute(data)// ctx.SetStrategy(&StratB{})// ctx.Execute(data)// Common Pattern Structuretrait ShippingStrategy { fn cost(&self, weight: f64) -> f64;}struct Standard;impl ShippingStrategy for Standard { fn cost(&self, weight: f64) -> f64 { weight * 5.0 }}
struct Checkout { strategy: Box<dyn ShippingStrategy>,}impl Checkout { fn total_shipping(&self, weight: f64) -> f64 { self.strategy.cost(weight) }}Remember
Section titled “Remember”- Strategy Pattern encapsulates algorithms into separate classes
- It enables runtime switching of algorithms
- It follows Open/Closed Principle - easy to add new strategies
- Use it when you need multiple interchangeable algorithms
- Don’t use it for simple cases where if/else is clearer!
Interview Focus: Strategy Pattern
Section titled “Interview Focus: Strategy Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable. The pattern lets the algorithm vary independently from clients that use it, enabling runtime behavior changes without modifying the client code.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of encapsulation
- Indicates you can explain concepts clearly
2. When to Use Strategy Pattern
Section titled “2. When to Use Strategy Pattern”Must mention:
- ✅ Multiple algorithms - Different ways to accomplish the same task
- ✅ Runtime switching - Need to change behavior dynamically
- ✅ Eliminate conditionals - Replace if/else chains
- ✅ Testing isolation - Test each algorithm independently
- ✅ Open/Closed Principle - Add algorithms without modifying existing code
Example scenario to give:
“I’d use Strategy Pattern when building a payment processing system. Each payment method - Credit Card, PayPal, Crypto - is a different strategy. The checkout process doesn’t care which payment method is used; it just calls the pay() method. Users can switch payment methods at runtime, and adding new payment methods is just creating a new strategy class.”
3. Strategy vs State Pattern
Section titled “3. Strategy vs State Pattern”Must discuss:
- Strategy - Client chooses the strategy explicitly, algorithms are interchangeable
- State - Object changes behavior based on internal state automatically
- Key difference - Who controls the switch (client vs object) and why
Example to give:
“Strategy Pattern is like choosing a shipping method at checkout - YOU choose between standard, express, or overnight. State Pattern is like a vending machine - it changes behavior automatically based on whether it has items, received payment, etc. With Strategy, the client decides. With State, the object decides based on its state.”
4. SOLID Principles Connection
Section titled “4. SOLID Principles Connection”Must discuss:
- Open/Closed Principle - Add new strategies without modifying context
- Single Responsibility - Each strategy handles one algorithm
- Dependency Inversion - Context depends on Strategy abstraction
- Interface Segregation - Strategy interface is focused and small
Example to give:
“Strategy Pattern strongly supports the Open/Closed Principle - you can add new sorting algorithms without modifying the SortingApplication class. It also supports Single Responsibility because each strategy class has one job - implementing one specific algorithm. The context depends on the Strategy interface, not concrete implementations, supporting Dependency Inversion.”
5. Benefits and Trade-offs
Section titled “5. Benefits and Trade-offs”Benefits to mention:
- Runtime flexibility - Change algorithms dynamically
- No conditionals - Eliminate if/else chains
- Easy testing - Test each strategy independently
- Code organization - Each algorithm in its own class
- Reusability - Strategies can be reused across contexts
Trade-offs to acknowledge:
- More classes - Each algorithm is a separate class
- Client awareness - Client must know about different strategies
- Complexity for simple cases - Overkill for 2-3 simple algorithms
- Configuration overhead - Need to configure and inject strategies
6. Common Interview Questions
Section titled “6. Common Interview Questions”Q: “How does Strategy Pattern eliminate conditionals?”
A:
“Instead of having a switch statement or if/else chain that checks which algorithm to use, we delegate to a strategy object. The context just calls strategy.execute() - it doesn’t know or care which concrete strategy is being used. Adding a new algorithm doesn’t require modifying any existing code, just creating a new strategy class.”
Q: “When would you NOT use Strategy Pattern?”
A:
“I wouldn’t use Strategy Pattern when there’s only one algorithm, when the algorithm never changes, or for simple 2-3 branch conditionals where the overhead isn’t justified. The pattern adds complexity with multiple classes, so for simple cases, a direct if/else might be clearer and more maintainable.”
Q: “How does Strategy Pattern relate to Dependency Injection?”
A:
“Strategy Pattern and Dependency Injection work together beautifully. The strategy is injected into the context, allowing the algorithm to be configured externally. This makes the context more flexible and testable - you can inject mock strategies in tests. In frameworks like Spring, strategies are often injected through constructor injection.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Strategy Pattern clearly in one sentence
- Explain when to use it (with examples)
- Describe the structure: Strategy, Concrete Strategy, Context
- Implement Strategy Pattern from scratch
- Compare with State Pattern
- List benefits and trade-offs
- Connect to SOLID principles
- Identify when NOT to use it
- Give 2-3 real-world examples (payment, sorting, compression)
- Discuss functional vs class-based strategies
Remember: Strategy Pattern is about interchangeable algorithms - define a family of algorithms, encapsulate each one, and swap them at runtime! 🔄
Make this lesson stick
Answer from memory, then check yourself. No typing or sign-in needed.
What problem does the Strategy Pattern solve?