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:
- Support multiple shipping calculation methods:
- Regular Shipping: Fixed shipping cost
- Express Shipping: Weight-based pricing
- International Shipping: Region and weight-based pricing
- System must be highly extensible:
- Easy to add new shipping calculation methods
- Avoid extensive if-else or switch-case statements
- 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:
- Maintenance Challenges
- Shipping calculation logic mixed with main business logic
- Adding or modifying calculation methods affects other parts
- Open-Closed Principle Violation
- Adding new shipping methods requires modifying core business logic
- 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:
- Strategy Interface
- Defines common interface for all algorithms
- Ensures consistent behavior across strategies
- Concrete Strategies
- Each strategy implements the interface
- Contains specific algorithm logic
- 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
π Related Design Patterns
- 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) }
}
}
π Related Articles
- Design Pattern 1: Object-Oriented Concepts
- Design Pattern 2: Design Principles
- State Pattern
- Command Pattern
- Template Method Pattern
β 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: