Strategy Pattern: Flexible Algorithms That Adapt to Context
Discover the Strategy Pattern - a design pattern that enables you to easily switch between different algorithms without modifying existing code. Learn how to apply this pattern to make your code more flexible and maintainable.
Table of Contents
No table of contents available for this article
1. Introduction to Strategy Pattern
Classification: Behavioral Pattern
Alias: Policy Pattern
Frequency of Use: ⭐⭐⭐⭐ (Very High)
Strategy Pattern is one of the most popular design patterns in object-oriented programming. This pattern helps you define a family of algorithms, encapsulate them into separate classes, and allow them to be interchangeable.
The strength of Strategy Pattern lies in its ability to completely separate the algorithm from the code that uses the algorithm, making it easy to change or extend functionality without affecting other parts of the system.
2. Real-World Problem & Solution
🔴 The Problem:
Imagine you're building a navigation app for cities, similar to Google Maps. Initially, the app only serves pedestrians, so you write a routing algorithm for walking directly in the main class.
csharp
public class Navigator {
public void FindRoute() {
// Routing algorithm for pedestrians
// Hardcoded directly in the method
}
}Everything works well... until requirements change!
Now, clients want:
🚗 Car routing (avoid one-way streets, prefer highways)
🚴 Bicycle routing (prefer bike lanes, avoid major roads)
🚌 Bus routing (follow fixed routes only)
If you keep adding if-else or switch-case statements to the existing code:
csharp
public void FindRoute(string vehicleType) {
if (vehicleType == "walking") {
// Code for pedestrians
} else if (vehicleType == "car") {
// Code for cars
} else if (vehicleType == "bicycle") {
// Code for bicycles
}
// ... growing increasingly bloated
}Consequences:
❌ Code becomes complex and hard to read
❌ Difficult to maintain: modifying one algorithm can affect others
❌ Violates Open-Closed Principle: must modify old code when adding new features
❌ Prone to bugs in currently working sections
🟢 Solution with Strategy Pattern:
Strategy Pattern proposes a completely different approach:
1. Separate each algorithm into its own class (called Strategy)
2. Define a common interface for all Strategies
3. Context class (Navigator) only needs to know the interface, not algorithm details
4. Client decides which Strategy to use
This way, when you need to add a new algorithm, simply create a new Strategy class without touching existing code!
┌─────────────┐ ┌──────────────────┐
│ Client │────────>│ Context │
└─────────────┘ │ - strategy │
│ + setStrategy() │
│ + executeLogic() │
└──────────────────┘
│
│ uses
▼
┌──────────────────┐
│ <<interface>> │
│ Strategy │
│ + algorithm() │
└──────────────────┘
△
│
┌───────────────┼───────────────┐
│ │ │
┌────────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐
│ ConcreteA │ │ ConcreteB │ │ ConcreteC │
│ + algorithm() │ │+ algorithm()│ │+ algorithm()│
└───────────────┘ └────────────┘ └────────────┘4. Strategy Pattern Architecture
Components:
1. Context
The class that uses Strategy objects
Doesn't know algorithm details, only communicates through the interface
Can change Strategy at runtime
2. Strategy Interface
Defines a common interface for all algorithms
Context uses this interface to invoke algorithms
3. Concrete Strategy
Implements different algorithms
Each class represents a different implementation approach
4. Client
Creates specific Strategy objects
Passes Strategy to Context for use
4. Advantages & Disadvantages
✅ Advantages:
1. Flexible Algorithm Switching
Can change algorithms at runtime without restarting the application
Easy to add new algorithms without affecting existing code
2. Follows SOLID Principles
Single Responsibility: Each Strategy handles only one algorithm
Open-Closed: Open for extension, closed for modification
3. Eliminates Complex Conditional Statements
Instead of lengthy
if-elseorswitch-caseblocks, just inject the appropriate Strategy
4. Code Reusability
Strategies can be used in different Contexts
5. Easy to Test
Can test each Strategy independently
Easy to mock Strategies for unit testing
❌ Disadvantages:
1. Increases Number of Classes
Each algorithm requires its own class → can complicate the project if overused
2. Should Not Be Used When:
Only have 2-3 algorithms that rarely change
Algorithms are too simple (just a few lines of code)
3. Client Must Understand Strategies
Client needs to know the differences between Strategies to choose correctly
Can be resolved by combining with Factory Pattern
5. When Should You Use Strategy Pattern?
Apply Strategy Pattern when:
✔️ Multiple processing methods exist for the same problem and need flexible switching
✔️ Want to separate business logic from implementation details
✔️ Code has many if-else or switch-case statements based on type or mode
✔️ Need to change algorithms at runtime (e.g., user configuration selection)
✔️ Have multiple similar classes differing only in certain behaviors
6. Real-World Example With C#
Problem:
In Adobe Illustrator, when exporting design files, users can choose from multiple formats: JPG, PNG, PDF, SVG,... Each format has its own processing method. Let's solve this problem using Strategy Pattern.
Step 1: Create Strategy Interface
public interface IExport
{
void ExportFile(string fileName);
}This interface defines common behavior: exporting files. All file export formats must implement this interface.
Step 2: Create Concrete Strategy Classes
Each file format gets its own class:
public class ExportJPG : IExport
{
public void ExportFile(string fileName)
{
Console.WriteLine($"Exporting file '{fileName}.JPG'...");
// JPG export logic (compression, quality, etc.)
Console.WriteLine($"✅ File '{fileName}.JPG' exported successfully!");
}
}
public class ExportPNG : IExport
{
public void ExportFile(string fileName)
{
Console.WriteLine($"Exporting file '{fileName}.PNG'...");
// PNG export logic (lossless compression, transparency, etc.)
Console.WriteLine($"✅ File '{fileName}.PNG' exported successfully!");
}
}
public class ExportPDF : IExport
{
public void ExportFile(string fileName)
{
Console.WriteLine($"Exporting file '{fileName}.PDF'...");
// PDF export logic (vector, fonts, layers, etc.)
Console.WriteLine($"✅ File '{fileName}.PDF' exported successfully!");
}
}Each class focuses on a specific file export algorithm, adhering to the Single Responsibility Principle.
Step 3: Create Context Class
Context is where Strategy is used:
public class ExportContext
{
private IExport _exportStrategy;
// Constructor injection
public ExportContext(IExport exportStrategy)
{
_exportStrategy = exportStrategy;
}
// Allow Strategy change at runtime
public void SetStrategy(IExport exportStrategy)
{
_exportStrategy = exportStrategy;
}
// Main method for execution
public void CreateArchive(string fileName)
{
_exportStrategy.ExportFile(fileName);
}
}Context doesn't care which Strategy is being used. It simply calls the ExportFile() method through the interface.
Step 4: Client Usage
csharp
class Client
{
static void Main(string[] args)
{
// Initialize Context with default Strategy (PNG)
ExportContext context = new ExportContext(new ExportPNG());
context.CreateArchive("Mushroom");
Console.WriteLine("\n--- Switching to JPG format ---\n");
// Change Strategy to JPG
context.SetStrategy(new ExportJPG());
context.CreateArchive("Mushroom");
Console.WriteLine("\n--- Switching to PDF format ---\n");
// Change Strategy to PDF
context.SetStrategy(new ExportPDF());
context.CreateArchive("Mushroom");
Console.ReadLine();
}
}
Exporting file 'Mushroom.PNG'...
✅ File 'Mushroom.PNG' exported successfully!
--- Switching to JPG format ---
Exporting file 'Mushroom.JPG'...
✅ File 'Mushroom.JPG' exported successfully!
--- Switching to PDF format ---
Exporting file 'Mushroom.PDF'...
✅ File 'Mushroom.PDF' exported successfully!Extension: Adding New Formats
Suppose you now need to add SVG format. You only need to:
public class ExportSVG : IExport
{
public void ExportFile(string fileName)
{
Console.WriteLine($"Exporting file '{fileName}.SVG'...");
// SVG export logic (vector format)
Console.WriteLine($"✅ File '{fileName}.SVG' exported successfully!");
}
}
// Usage
context.SetStrategy(new ExportSVG());
context.CreateArchive("Mushroom");No need to modify a single line of code in ExportContext, IExport, or other Strategies! This is the power of the Open-Closed Principle.
7. Comparison With Related Design Patterns
Strategy vs State Pattern
Similarities:
Both based on composition
Both allow runtime behavior changes
Differences:
Strategy PatternState PatternStrategies are independent of each otherStates can know about each other and transitionClient actively selects StrategyState automatically transitions between statesFocuses on algorithmsFocuses on statesExamples:
Strategy: Choose payment method (Credit Card, PayPal, Bitcoin)
State: Order status (Pending → Processing → Shipped → Delivered)
Strategy vs Bridge Pattern
Similarities:
Both based on composition
Both separate abstraction from implementation
Differences:
Strategy PatternBridge PatternFocuses on choosing algorithmsFocuses on separating interface from implementationOne Context has one Strategy at a timeAbstraction and Implementation are independent, evolve separatelySolves behavioral problemsSolves structural problemsStrategy vs Command Pattern
Similarities:
Both encapsulate behavior into objects
Both can pass behavior as parameters
Differences:
Strategy PatternCommand PatternStrategies are typically statelessCommands typically have state (store request information)Focuses on how to executeFocuses on what to executeDoesn't support undo/redoSupports undo/redo, queuing8. Tips & Best Practices
💡 Use Dependency Injection
// Good: Inject via constructor
public class PaymentProcessor
{
private readonly IPaymentStrategy _strategy;
public PaymentProcessor(IPaymentStrategy strategy)
{
_strategy = strategy;
}
}💡 Combine with Factory Pattern
public class ExportStrategyFactory
{
public static IExport GetStrategy(string fileType)
{
return fileType.ToLower() switch
{
"jpg" => new ExportJPG(),
"png" => new ExportPNG(),
"pdf" => new ExportPDF(),
_ => throw new ArgumentException("Unsupported file type")
};
}
}💡 Use Clear Naming
Strategy names should clearly describe the algorithm:
QuickSortStrategy,BubbleSortStrategyAvoid generic names like
StrategyA,StrategyB
💡 Consider Lambda/Delegate in .NET
For simple Strategies, use Func or Action:
csharp
public class SimpleContext
{
private Action<string> _strategy;
public SimpleContext(Action<string> strategy)
{
_strategy = strategy;
}
public void Execute(string data)
{
_strategy(data);
}
}
// Usage
var context = new SimpleContext(data => Console.WriteLine($"Process: {data}"));
context.Execute("test");Conclusion
Strategy Pattern is a powerful tool that makes your code:
✅ Flexible - Easy to change algorithms
✅ Maintainable - Each algorithm is isolated
✅ Extensible - Adding new algorithms doesn't affect existing code
✅ Testable - Easy to unit test each Strategy
However, don't overuse it! Only apply when:
Have 3 or more algorithms
Algorithms are likely to change or expand
Want to eliminate complex conditional logic
Apply Strategy Pattern wisely to make your code more professional and maintainable!
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns