Strategy Pattern
🎯 Explain Like I'm 5...
Imagine you want to get to your friend's house for a playdate! You can choose HOW to get there - each way is a different 'strategy'! 🏠
😟 The Problem:
- • Your friend lives 2 miles away
- • You could walk, bike, have mom drive you, or even fly with a jetpack!
- • Which way is best depends on: weather, time, how tired you are, how cool you want to look! 😎
💡 The Solution - Pick Your Strategy!
- • Sunny day + have time? WALK! 🚶
- • Want exercise + faster? BIKE! 🚴
- • Raining + far away? CAR! 🚗
- • Want to be super cool? JETPACK! 🚀 (okay, maybe not...)
🌟 The Key Idea:
You're doing the SAME thing (getting to friend's house), but you can CHOOSE the METHOD! Each method is a 'strategy' that does the job differently. You can easily SWITCH strategies based on what works best!
🚀 Why Is This Pattern Useful?
- ✨Swap algorithms easily without changing your main code!
- ✨Add new strategies without touching existing ones!
- ✨Choose the best method at RUNTIME based on conditions!
- ✨Keep your code clean - each strategy lives in its own class!
📋 Pattern Purpose
The Strategy pattern defines a FAMILY of algorithms, encapsulates each one, and makes them INTERCHANGEABLE. Strategy lets the algorithm vary independently from clients that use it. Instead of implementing a single algorithm directly, the code receives runtime instructions about which algorithm to use from a family of algorithms.
⚡ When to Use Strategy Pattern
- ✓You have multiple algorithms for a specific task and want to switch between them
- ✓You want to avoid multiple conditional statements (if/else, switch) for selecting algorithms
- ✓Related algorithms have different implementations but similar interfaces
- ✓You need to hide complex algorithm-specific data from clients
- ✓You want to change behavior at runtime
🧩 Strategy Pattern Components
Strategy Interface:
Declares the common interface for all concrete strategies
Concrete Strategy:
Implements the algorithm using the Strategy interface
Context:
Maintains a reference to a Strategy object and uses it to call the algorithm
💻 Java Implementations
Example 1: Payment Methods
Different payment strategies (CreditCard, PayPal, Bitcoin) that can be swapped at runtime.
// Strategy Interface - defines the algorithm contractpublic interface PaymentStrategy { void pay(double amount); String getPaymentType();}// Concrete Strategy 1 - Credit Card Paymentpublic class CreditCardStrategy implements PaymentStrategy { private String cardNumber; private String cvv; private String expiryDate; public CreditCardStrategy(String cardNumber, String cvv, String expiryDate) { this.cardNumber = cardNumber; this.cvv = cvv; this.expiryDate = expiryDate; } @Override public void pay(double amount) { System.out.println("Paying $" + amount + " using Credit Card"); System.out.println("Card Number: " + maskCardNumber(cardNumber)); System.out.println("Processing payment..."); // Payment processing logic here } @Override public String getPaymentType() { return "Credit Card"; } private String maskCardNumber(String cardNumber) { return "**** **** **** " + cardNumber.substring(cardNumber.length() - 4); }}// Concrete Strategy 2 - PayPal Paymentpublic class PayPalStrategy implements PaymentStrategy { private String email; private String password; public PayPalStrategy(String email, String password) { this.email = email; this.password = password; } @Override public void pay(double amount) { System.out.println("Paying $" + amount + " using PayPal"); System.out.println("Email: " + email); System.out.println("Authenticating..."); // PayPal API integration here } @Override public String getPaymentType() { return "PayPal"; }}// Concrete Strategy 3 - Bitcoin Paymentpublic class BitcoinStrategy implements PaymentStrategy { private String walletAddress; public BitcoinStrategy(String walletAddress) { this.walletAddress = walletAddress; } @Override public void pay(double amount) { System.out.println("Paying $" + amount + " using Bitcoin"); System.out.println("Wallet: " + walletAddress); System.out.println("Converting to BTC..."); System.out.println("Broadcasting transaction to blockchain..."); // Bitcoin transaction logic here } @Override public String getPaymentType() { return "Bitcoin"; }}// Context - uses a PaymentStrategyimport java.util.ArrayList;import java.util.List;public class ShoppingCart { private List<Item> items; private PaymentStrategy paymentStrategy; public ShoppingCart() { this.items = new ArrayList<>(); } public void addItem(Item item) { items.add(item); } public double calculateTotal() { return items.stream() .mapToDouble(Item::getPrice) .sum(); } // Strategy can be set at runtime! public void setPaymentStrategy(PaymentStrategy strategy) { this.paymentStrategy = strategy; } public void checkout() { if (paymentStrategy == null) { System.out.println("Please select a payment method!"); return; } double total = calculateTotal(); System.out.println("\n=== Checkout ==="); System.out.println("Total: $" + total); System.out.println("Payment Method: " + paymentStrategy.getPaymentType()); paymentStrategy.pay(total); System.out.println("Payment successful!\n"); }}class Item { private String name; private double price; public Item(String name, double price) { this.name = name; this.price = price; } public double getPrice() { return price; }}// Client code demonstrating runtime strategy switchingpublic class PaymentDemo { public static void main(String[] args) { ShoppingCart cart = new ShoppingCart(); // Add items to cart cart.addItem(new Item("Laptop", 999.99)); cart.addItem(new Item("Mouse", 29.99)); cart.addItem(new Item("Keyboard", 79.99)); // Scenario 1: Pay with Credit Card System.out.println("Customer chooses Credit Card:"); cart.setPaymentStrategy(new CreditCardStrategy( "1234567890123456", "123", "12/25" )); cart.checkout(); // Scenario 2: Pay with PayPal System.out.println("Customer chooses PayPal:"); cart.setPaymentStrategy(new PayPalStrategy( "customer@email.com", "password123" )); cart.checkout(); // Scenario 3: Pay with Bitcoin System.out.println("Customer chooses Bitcoin:"); cart.setPaymentStrategy(new BitcoinStrategy( "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" )); cart.checkout(); // The cart works with ANY payment strategy! // We can easily add more payment methods without changing ShoppingCart! }}Example 2: Sorting Algorithms
Different sorting strategies (BubbleSort, QuickSort, MergeSort) for different data sizes.
// Strategy Interfacepublic interface SortStrategy { void sort(int[] array); String getName();}// Concrete Strategy 1 - Good for small datasetspublic class BubbleSortStrategy implements SortStrategy { @Override public void sort(int[] array) { System.out.println("Sorting using Bubble Sort (O(n²))"); int n = array.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (array[j] > array[j + 1]) { // Swap int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } @Override public String getName() { return "Bubble Sort"; }}// Concrete Strategy 2 - Good for general usepublic class QuickSortStrategy implements SortStrategy { @Override public void sort(int[] array) { System.out.println("Sorting using Quick Sort (O(n log n))"); quickSort(array, 0, array.length - 1); } 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; } @Override public String getName() { return "Quick Sort"; }}// Concrete Strategy 3 - Best for large datasetspublic class MergeSortStrategy implements SortStrategy { @Override public void sort(int[] array) { System.out.println("Sorting using Merge Sort (O(n log n))"); mergeSort(array, 0, array.length - 1); } private void mergeSort(int[] arr, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } } private void merge(int[] arr, int left, int mid, int right) { int n1 = mid - left + 1; int n2 = right - mid; int[] L = new int[n1]; int[] R = new int[n2]; System.arraycopy(arr, left, L, 0, n1); System.arraycopy(arr, mid + 1, R, 0, n2); int i = 0, j = 0, k = left; 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++]; } @Override public String getName() { return "Merge Sort"; }}// Context - chooses sorting strategy based on data sizeimport java.util.Arrays;public class DataSorter { private SortStrategy strategy; public void setStrategy(SortStrategy strategy) { this.strategy = strategy; } public void performSort(int[] data) { if (strategy == null) { System.out.println("No sorting strategy selected!"); return; } System.out.println("Before: " + Arrays.toString(data)); long startTime = System.nanoTime(); strategy.sort(data); long endTime = System.nanoTime(); System.out.println("After: " + Arrays.toString(data)); System.out.println("Time taken: " + (endTime - startTime) / 1_000_000.0 + " ms\n"); } // Smart selection based on array size public void autoSelectStrategy(int arraySize) { if (arraySize < 10) { setStrategy(new BubbleSortStrategy()); System.out.println("Auto-selected: Bubble Sort (small dataset)"); } else if (arraySize < 1000) { setStrategy(new QuickSortStrategy()); System.out.println("Auto-selected: Quick Sort (medium dataset)"); } else { setStrategy(new MergeSortStrategy()); System.out.println("Auto-selected: Merge Sort (large dataset)"); } } public static void main(String[] args) { DataSorter sorter = new DataSorter(); // Small dataset int[] smallData = {64, 34, 25, 12, 22, 11, 90}; sorter.autoSelectStrategy(smallData.length); sorter.performSort(smallData); // Medium dataset int[] mediumData = {5, 2, 9, 1, 7, 6, 3}; sorter.autoSelectStrategy(mediumData.length); sorter.performSort(mediumData); // Manual strategy selection int[] customData = {3, 7, 1, 9, 2}; System.out.println("Manual selection: Merge Sort"); sorter.setStrategy(new MergeSortStrategy()); sorter.performSort(customData); }}Example 3: Navigation Strategies
Different travel strategies (Walk, Bike, Car) based on distance and conditions.
// Strategy Interfacepublic interface NavigationStrategy { void navigate(String from, String to); double estimateTime(double distance); String getMode();}// Concrete Strategy 1 - Walkingpublic class WalkStrategy implements NavigationStrategy { private static final double WALK_SPEED = 5.0; // km/h @Override public void navigate(String from, String to) { System.out.println("🚶 Walking directions from " + from + " to " + to); System.out.println("- Take the pedestrian path"); System.out.println("- Use crosswalks"); System.out.println("- Enjoy the scenery!"); } @Override public double estimateTime(double distance) { return (distance / WALK_SPEED) * 60; // minutes } @Override public String getMode() { return "Walking"; }}// Concrete Strategy 2 - Bikingpublic class BikeStrategy implements NavigationStrategy { private static final double BIKE_SPEED = 15.0; // km/h @Override public void navigate(String from, String to) { System.out.println("🚴 Biking directions from " + from + " to " + to); System.out.println("- Follow bike lanes when available"); System.out.println("- Watch for traffic"); System.out.println("- Use bike-friendly routes"); } @Override public double estimateTime(double distance) { return (distance / BIKE_SPEED) * 60; // minutes } @Override public String getMode() { return "Biking"; }}// Concrete Strategy 3 - Drivingpublic class CarStrategy implements NavigationStrategy { private static final double CAR_SPEED = 50.0; // km/h (city average) @Override public void navigate(String from, String to) { System.out.println("🚗 Driving directions from " + from + " to " + to); System.out.println("- Take main roads"); System.out.println("- Follow traffic signals"); System.out.println("- Watch for speed limits"); System.out.println("- Parking may be required"); } @Override public double estimateTime(double distance) { // Add 10 minutes for traffic and parking return (distance / CAR_SPEED) * 60 + 10; // minutes } @Override public String getMode() { return "Driving"; }}// Context - Navigation systempublic class Navigator { private NavigationStrategy strategy; private String currentLocation; public Navigator(String currentLocation) { this.currentLocation = currentLocation; } public void setStrategy(NavigationStrategy strategy) { this.strategy = strategy; } public void routeTo(String destination, double distance) { if (strategy == null) { System.out.println("Please select a navigation mode!"); return; } System.out.println("\n=== Navigation ==="); System.out.println("Mode: " + strategy.getMode()); System.out.println("Distance: " + distance + " km"); double time = strategy.estimateTime(distance); System.out.println("Estimated time: " + String.format("%.1f", time) + " minutes"); System.out.println(); strategy.navigate(currentLocation, destination); System.out.println("==================\n"); } // Smart strategy selection based on conditions public void autoSelectStrategy(double distance, boolean isRaining, boolean hasTime) { if (distance < 1.0 && hasTime) { setStrategy(new WalkStrategy()); System.out.println("Auto-selected: Walking (short distance)"); } else if (distance < 5.0 && !isRaining) { setStrategy(new BikeStrategy()); System.out.println("Auto-selected: Biking (good weather, medium distance)"); } else { setStrategy(new CarStrategy()); System.out.println("Auto-selected: Driving (long distance or bad weather)"); } } public static void main(String[] args) { Navigator nav = new Navigator("Home"); // Scenario 1: Short trip, nice weather System.out.println("Scenario 1: Going to nearby park (0.8 km)"); nav.autoSelectStrategy(0.8, false, true); nav.routeTo("Central Park", 0.8); // Scenario 2: Medium distance, rainy System.out.println("Scenario 2: Going to work (4 km), raining"); nav.autoSelectStrategy(4.0, true, true); nav.routeTo("Office", 4.0); // Scenario 3: Long distance System.out.println("Scenario 3: Going to mall (12 km)"); nav.autoSelectStrategy(12.0, false, true); nav.routeTo("Shopping Mall", 12.0); // Manual override System.out.println("Scenario 4: User manually chooses to bike"); nav.setStrategy(new BikeStrategy()); nav.routeTo("Coffee Shop", 3.0); }}🌍 Real-World Examples
- 💳Payment Processing: Credit card, PayPal, cryptocurrency, bank transfer
- 🗜️Compression Algorithms: ZIP, RAR, 7Z based on file type and size
- 🗺️Route Planning: Walking, driving, public transit, cycling directions
- 📝Logging: Console logging, file logging, database logging, cloud logging
- 🖼️Image Rendering: PNG, JPEG, WebP, SVG based on quality and size needs
- ✅Data Validation: Email, phone, credit card, password validators
✅ Benefits
- ✅Open/Closed Principle: Easy to add new strategies without modifying context
- ✅Single Responsibility: Each strategy class has one job
- ✅Runtime Flexibility: Switch strategies dynamically based on conditions
- ✅Eliminates Conditionals: Replace complex if/else chains with strategy objects
- ✅Testing: Each strategy can be tested independently
- ✅Code Reuse: Strategies can be reused across different contexts
⚠️ Drawbacks
- ⚠️More Classes: Creates many strategy classes for simple cases
- ⚠️Complexity: Can be overkill if you only have 2-3 algorithms that rarely change
- ⚠️Client Awareness: Clients must know about different strategies to choose one
- ⚠️Communication Overhead: Context and strategies must share data somehow
🔑 Key Points to Remember
- 1️⃣Strategy encapsulates algorithms into separate classes with a common interface
- 2️⃣Context delegates work to a strategy object instead of implementing multiple variants
- 3️⃣Strategies are INTERCHANGEABLE - context doesn't care which one is used
- 4️⃣Use Strategy when you have many related classes that differ only in behavior
- 5️⃣Different from State pattern - Strategy focuses on algorithm selection, State on object state transitions
💪 Practice Scenarios
- • Build a text formatting system with strategies: UpperCase, LowerCase, TitleCase, CamelCase
- • Create a discount calculator with strategies: PercentageDiscount, FixedAmountDiscount, BuyOneGetOne
- • Implement a data export system with strategies: CSVExporter, JSONExporter, XMLExporter, PDFExporter
- • Design a shipping cost calculator: StandardShipping, ExpressShipping, OvernightShipping, InternationalShipping
- • Create a notification system: EmailNotification, SMSNotification, PushNotification, SlackNotification