Design Pattern 25: Strategy Pattern - Complete Guide with Real-World Examples

πŸ“ Download the complete Design Pattern series code from our design_pattern repository.


🎯 What is the Strategy Pattern?

The Strategy Pattern is a behavioral design pattern that enables you to define a family of algorithms, encapsulate each one, and make them interchangeable. It allows the algorithm to vary independently from clients that use it, providing flexibility and maintainability.

Key Benefits:

  • βœ… Algorithm flexibility - Switch algorithms at runtime
  • βœ… Low coupling - Algorithms are isolated from client code
  • βœ… Easy extension - Add new strategies without modifying existing code
  • βœ… Single responsibility - Each strategy focuses on one algorithm
  • βœ… Open/Closed Principle - Open for extension, closed for modification

πŸš€ Real-World Problem: E-commerce Shipping Calculator

Let’s design an e-commerce shipping cost calculation system with the following requirements:

System Requirements:

  1. Support multiple shipping calculation methods:
    • Regular Shipping: Fixed shipping cost
    • Express Shipping: Weight-based pricing
    • International Shipping: Region and weight-based pricing
  2. System must be highly extensible:
    • Easy to add new shipping calculation methods
  3. Avoid extensive if-else or switch-case statements
  4. Users should easily switch between shipping methods

Business Rules:

  • Regular shipping: Fixed cost regardless of weight/region
  • Express shipping: Cost per kilogram
  • International shipping: Variable cost based on region and weight
  • System should support future shipping methods without code changes

πŸ—οΈ Object-Oriented Analysis (OOA)

Let’s analyze the problem and identify the core components:

Identified Forces:

  1. Maintenance Challenges
    • Shipping calculation logic mixed with main business logic
    • Adding or modifying calculation methods affects other parts
  2. Open-Closed Principle Violation
    • Adding new shipping methods requires modifying core business logic
  3. Single Responsibility Principle Violation
    • Main class handles both shipping calculation and core business logic

πŸ’‘ Strategy Pattern Solution

After analyzing the forces, we can apply the Strategy Pattern to encapsulate algorithms into separate classes:

Strategy Pattern Components:

  1. Strategy Interface
    • Defines common interface for all algorithms
    • Ensures consistent behavior across strategies
  2. Concrete Strategies
    • Each strategy implements the interface
    • Contains specific algorithm logic
  3. Context
    • Maintains reference to current strategy
    • Delegates work to strategy object

Benefits:

  • Algorithm isolation from client code
  • Runtime strategy switching capability
  • Easy extension without modifying existing code

πŸ› οΈ Implementation: E-commerce Shipping Calculator

Here’s the complete implementation using the Strategy Pattern:

1. Strategy Interface

interface ShippingStrategy {
    fun calculateShippingCost(weight: Double, region: String): Double
    fun getStrategyName(): String
}

2. Concrete Strategy Classes

class RegularShipping : ShippingStrategy {
    override fun calculateShippingCost(weight: Double, region: String): Double {
        return 50.0 // Fixed shipping cost
    }
    
    override fun getStrategyName(): String = "Regular Shipping"
}

class ExpressShipping : ShippingStrategy {
    override fun calculateShippingCost(weight: Double, region: String): Double {
        return weight * 10 // 10 per kilogram
    }
    
    override fun getStrategyName(): String = "Express Shipping"
}

class InternationalShipping : ShippingStrategy {
    override fun calculateShippingCost(weight: Double, region: String): Double {
        val regionMultiplier = when (region) {
            "Asia" -> 15
            "Europe" -> 20
            "America" -> 25
            else -> 30
        }
        return weight * regionMultiplier
    }
    
    override fun getStrategyName(): String = "International Shipping"
}

3. Context Class

class ShippingCalculator(private var strategy: ShippingStrategy) {
    
    fun setStrategy(strategy: ShippingStrategy) {
        this.strategy = strategy
        println("πŸ”„ Strategy changed to: ${strategy.getStrategyName()}")
    }
    
    fun calculateCost(weight: Double, region: String): Double {
        val cost = strategy.calculateShippingCost(weight, region)
        println("πŸ“¦ ${strategy.getStrategyName()}: $${cost} for ${weight}kg to $region")
        return cost
    }
    
    fun getCurrentStrategy(): String = strategy.getStrategyName()
}

4. Client Code

fun main() {
    println("=== E-commerce Shipping Calculator Demo ===")
    
    val calculator = ShippingCalculator(RegularShipping())
    
    // Test different strategies
    val testWeight = 5.0
    val testRegion = "Asia"
    
    // Regular shipping
    calculator.calculateCost(testWeight, testRegion)
    
    // Switch to express shipping
    calculator.setStrategy(ExpressShipping())
    calculator.calculateCost(testWeight, testRegion)
    
    // Switch to international shipping
    calculator.setStrategy(InternationalShipping())
    calculator.calculateCost(testWeight, testRegion)
    
    // Test different regions
    println("\n=== Regional Cost Comparison ===")
    val regions = listOf("Asia", "Europe", "America", "Africa")
    regions.forEach { region ->
        calculator.calculateCost(2.0, region)
    }
}

Expected Output:

=== E-commerce Shipping Calculator Demo ===
πŸ“¦ Regular Shipping: $50.0 for 5.0kg to Asia
πŸ”„ Strategy changed to: Express Shipping
πŸ“¦ Express Shipping: $50.0 for 5.0kg to Asia
πŸ”„ Strategy changed to: International Shipping
πŸ“¦ International Shipping: $75.0 for 5.0kg to Asia

=== Regional Cost Comparison ===
πŸ“¦ International Shipping: $30.0 for 2.0kg to Asia
πŸ“¦ International Shipping: $40.0 for 2.0kg to Europe
πŸ“¦ International Shipping: $50.0 for 2.0kg to America
πŸ“¦ International Shipping: $60.0 for 2.0kg to Africa

πŸ”§ Advanced Implementation: Enhanced Strategy Pattern

Let’s create a more sophisticated version with strategy validation and factory pattern:

// Enhanced strategy with validation
interface EnhancedShippingStrategy : ShippingStrategy {
    fun validateOrder(weight: Double, region: String): Boolean
    fun getEstimatedDeliveryDays(): Int
}

class PremiumShipping : EnhancedShippingStrategy {
    override fun calculateShippingCost(weight: Double, region: String): Double {
        return weight * 25 + 100 // Premium base cost + weight-based
    }
    
    override fun getStrategyName(): String = "Premium Shipping"
    
    override fun validateOrder(weight: Double, region: String): Boolean {
        return weight <= 50.0 // Premium shipping has weight limit
    }
    
    override fun getEstimatedDeliveryDays(): Int = 1
}

// Strategy factory
object ShippingStrategyFactory {
    fun createStrategy(type: String): ShippingStrategy {
        return when (type.lowercase()) {
            "regular" -> RegularShipping()
            "express" -> ExpressShipping()
            "international" -> InternationalShipping()
            "premium" -> PremiumShipping()
            else -> throw IllegalArgumentException("Unknown shipping type: $type")
        }
    }
}

πŸ“Š Strategy Pattern vs Alternative Approaches

Approach Pros Cons
Strategy Pattern βœ… Clean separation
βœ… Easy to extend
βœ… Runtime switching
❌ More classes
❌ Slight overhead
If-Else Chains βœ… Simple for few cases ❌ Hard to maintain
❌ Violates OCP
Switch Statements βœ… Type-safe
βœ… Compact
❌ Mixed responsibilities
❌ Hard to extend
Inheritance βœ… Reuse code ❌ Tight coupling
❌ Inflexible

🎯 When to Use the Strategy Pattern

βœ… Perfect For:

  • Multiple algorithms for the same task
  • Runtime algorithm selection
  • Complex conditional logic
  • Algorithm families with similar interfaces
  • Testing different algorithms

❌ Avoid When:

  • Simple conditional logic (use if-else instead)
  • Performance-critical applications
  • Few algorithm variations
  • Static algorithm selection

  • State Pattern: Similar structure, but for state-dependent behavior
  • Command Pattern: Can encapsulate algorithms as commands
  • Template Method Pattern: For algorithm skeletons with variations
  • Factory Pattern: Often used together for strategy creation

πŸ“ˆ Real-World Applications

1. Payment Processing Systems

interface PaymentStrategy {
    fun processPayment(amount: Double): Boolean
}

class CreditCardPayment : PaymentStrategy { /* Implementation */ }
class PayPalPayment : PaymentStrategy { /* Implementation */ }
class CryptocurrencyPayment : PaymentStrategy { /* Implementation */ }

2. Sorting Algorithms

interface SortStrategy<T> {
    fun sort(list: List<T>): List<T>
}

class QuickSort<T> : SortStrategy<T> { /* Implementation */ }
class MergeSort<T> : SortStrategy<T> { /* Implementation */ }
class BubbleSort<T> : SortStrategy<T> { /* Implementation */ }

3. Discount Calculation

interface DiscountStrategy {
    fun calculateDiscount(price: Double, customer: Customer): Double
}

class PercentageDiscount : DiscountStrategy { /* Implementation */ }
class FixedAmountDiscount : DiscountStrategy { /* Implementation */ }
class SeasonalDiscount : DiscountStrategy { /* Implementation */ }

4. Data Compression

interface CompressionStrategy {
    fun compress(data: ByteArray): ByteArray
    fun decompress(data: ByteArray): ByteArray
}

class GzipCompression : CompressionStrategy { /* Implementation */ }
class LZ4Compression : CompressionStrategy { /* Implementation */ }
class ZstdCompression : CompressionStrategy { /* Implementation */ }

🚨 Common Pitfalls and Best Practices

1. Strategy Selection

// ❌ Avoid: Hard-coded strategy selection
val strategy = if (condition) StrategyA() else StrategyB()

// βœ… Prefer: Factory or configuration-based selection
val strategy = StrategyFactory.createStrategy(type)

2. Strategy Validation

// βœ… Good: Validate strategy before use
fun calculateCost(weight: Double, region: String): Double {
    if (strategy is EnhancedShippingStrategy) {
        if (!strategy.validateOrder(weight, region)) {
            throw IllegalArgumentException("Invalid order for ${strategy.getStrategyName()}")
        }
    }
    return strategy.calculateShippingCost(weight, region)
}

3. Strategy Composition

// βœ… Good: Combine multiple strategies
class CompositeShippingStrategy(private val strategies: List<ShippingStrategy>) : ShippingStrategy {
    override fun calculateShippingCost(weight: Double, region: String): Double {
        return strategies.sumOf { it.calculateShippingCost(weight, region) }
    }
}


βœ… Conclusion

Through the Strategy Pattern, we successfully separated shipping calculation logic from core functionality, achieving the following benefits:

Key Advantages:

  • 🎯 Easy extension - Add new shipping methods without modifying existing code
  • πŸ”§ Low coupling - Shipping logic isolated from business logic
  • πŸ“ˆ Runtime flexibility - Switch strategies dynamically
  • πŸ›‘οΈ Better maintainability - Clear separation of concerns

Design Principles Followed:

  • Single Responsibility Principle (SRP): Each strategy class has one responsibility
  • Open-Closed Principle (OCP): Open for extension, closed for modification
  • Dependency Inversion Principle (DIP): Depend on abstractions, not concretions

Perfect For:

  • Different discount strategies (percentage, fixed amount, seasonal)
  • Various sorting algorithms (quick sort, merge sort, bubble sort)
  • Multiple tax calculation methods (progressive, flat, regional)
  • Payment processing systems (credit card, PayPal, cryptocurrency)

The Strategy Pattern provides an elegant solution for complex conditional logic and makes your system more flexible and maintainable!


πŸ’‘ Pro Tip: Combine the Strategy Pattern with the Factory Pattern to create strategies dynamically based on configuration or user input.

πŸ”” Stay Updated: Follow our Design Pattern series for more software architecture insights!




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • How to Use Multiple GitHub Accounts on One Computer: Complete SSH Setup Guide
  • Excalidraw AI: Create Professional Diagrams with Text Commands - Complete Guide
  • Complete macOS Development Environment Setup Guide for 2024
  • Design Pattern 28: Interpreter Pattern - Complete Guide with Examples
  • Design Pattern 27: Visitor Pattern - Complete Guide with Real-World IoT Examples