Mastering Design Patterns in C# and ASP.NET Core
Part 4.9 – Strategy Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐☆☆ Intermediate
Prerequisites: C#, OOP, Interfaces, SOLID Principles, Dependency Injection, ASP.NET Core
Table of Contents
Introduction
What is the Strategy Design Pattern?
Why Do We Need the Strategy Pattern?
The Problem with Large
if-elseandswitchStatementsReal-World Analogy
Strategy Pattern Terminology
How the Strategy Pattern Works
UML Class Diagram
Components of the Strategy Pattern
Complete C# Console Application
Payment Processing Example
Discount Calculation Example
ASP.NET Core Implementation
Strategy Pattern with Dependency Injection
Strategy Factory
Real-World Enterprise Scenarios
Strategy vs State
Strategy vs Command
Strategy vs Template Method
Strategy vs Chain of Responsibility
Advantages
Disadvantages
Best Practices
Common Mistakes
Unit Testing Strategies
Interview Questions
When Should You Use Strategy?
When Should You Avoid Strategy?
Key Takeaways
Conclusion
Coming Up Next
1. Introduction
Modern applications frequently need to perform the same business operation in multiple ways.
For example, an e-commerce application might support:
Credit Card
PayPal
Bank Transfer
Digital Wallet
All of them perform the same high-level operation:
Make Payment
However, the implementation is different.
A naive approach might look like:
if (paymentType == "CreditCard")
{
// Credit card logic
}
else if (paymentType == "PayPal")
{
// PayPal logic
}
else if (paymentType == "BankTransfer")
{
// Bank transfer logic
}
This approach works initially.
But as the application grows:
Credit Card
PayPal
Bank Transfer
Apple Pay
Google Pay
Wallet
Buy Now Pay Later
Cryptocurrency
Corporate Account
the conditional logic becomes difficult to maintain.
The Strategy Design Pattern solves this problem by encapsulating each algorithm or business rule into its own class.
2. What is the Strategy Design Pattern?
The Strategy Design Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
In simple terms:
Strategy Pattern allows us to select an algorithm or business rule at runtime without changing the code that uses it.
For example:
PaymentService
|
+---- CreditCardStrategy
|
+---- PayPalStrategy
|
+---- BankTransferStrategy
The application can choose the appropriate strategy at runtime.
3. Why Do We Need the Strategy Pattern?
Imagine this service:
public class PaymentService
{
public void Pay(
string paymentType,
decimal amount)
{
if (paymentType == "CreditCard")
{
// Credit Card processing
}
else if (paymentType == "PayPal")
{
// PayPal processing
}
else if (paymentType == "BankTransfer")
{
// Bank Transfer processing
}
}
}
This creates several problems.
Problem 1 – Large Conditional Logic
The service becomes increasingly large.
Problem 2 – Difficult Maintenance
Changing one payment algorithm requires modifying the service.
Problem 3 – Violates Open/Closed Principle
Adding a new payment method requires modifying existing code.
Problem 4 – Difficult Testing
One class contains many independent algorithms.
Problem 5 – Tight Coupling
The service knows about every payment implementation.
The Strategy Pattern addresses these problems.
4. The Problem with Large if-else and switch Statements
Consider:
switch (paymentType)
{
case "CreditCard":
// ...
break;
case "PayPal":
// ...
break;
case "BankTransfer":
// ...
break;
case "Wallet":
// ...
break;
}
Now suppose the business adds:
UPI
Apple Pay
Google Pay
Corporate Card
Installment Payment
The switch keeps growing.
A better design is:
IPaymentStrategy
|
+---- CreditCardPaymentStrategy
+---- PayPalPaymentStrategy
+---- BankTransferPaymentStrategy
+---- WalletPaymentStrategy
Now each algorithm is isolated.
5. Real-World Analogy
Consider navigation software.
You want to travel from:
Home → Airport
The destination is the same.
But you may choose:
Car
Bus
Train
Walking
The goal is the same:
Reach Destination
The algorithm changes depending on the selected strategy.
Navigation
|
+---- CarRouteStrategy
+---- BusRouteStrategy
+---- TrainRouteStrategy
+---- WalkingRouteStrategy
This is the essence of the Strategy Pattern.
6. Strategy Pattern Terminology
The Strategy Pattern typically contains three important components.
Strategy
An interface or abstraction defining the algorithm.
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
Concrete Strategy
A class implementing a specific algorithm.
CreditCardStrategy
PayPalStrategy
BankTransferStrategy
Context
The class that uses a strategy.
PaymentService
The structure is:
+------------------+
| Context |
+------------------+
| - strategy |
+--------+---------+
|
↓
+------------------+
| IStrategy |
+------------------+
| + Execute() |
+--------+---------+
|
+-----------+-----------+
| | |
↓ ↓ ↓
Strategy A Strategy B Strategy C
7. How the Strategy Pattern Works
Suppose we have:
PaymentService
and:
IPaymentStrategy
At runtime:
User selects PayPal
↓
PayPalStrategy
↓
PaymentService
↓
Execute payment
For Credit Card:
User selects Credit Card
↓
CreditCardStrategy
↓
PaymentService
↓
Execute payment
The Context doesn't need to know how the algorithm works.
It simply calls:
_strategy.Pay(amount);
8. UML Class Diagram
+----------------------+
| PaymentService |
+----------------------+
| - strategy |
+----------------------+
| + SetStrategy() |
| + ProcessPayment() |
+----------+-----------+
|
↓
+----------------------+
| <<interface>> |
| IPaymentStrategy |
+----------------------+
| + Pay(amount) |
+----------+-----------+
|
+---------------+---------------+
| | |
↓ ↓ ↓
+---------------+ +---------------+ +---------------+
| CreditCard | | PayPal | | BankTransfer |
| Strategy | | Strategy | | Strategy |
+---------------+ +---------------+ +---------------+
| Pay() | | Pay() | | Pay() |
+---------------+ +---------------+ +---------------+
9. Components of the Strategy Pattern
9.1 Strategy Interface
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
It defines the common contract.
9.2 Concrete Strategies
public class CreditCardStrategy
: IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine(
$"Paid ${amount} using Credit Card.");
}
}
Another:
public class PayPalStrategy
: IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine(
$"Paid ${amount} using PayPal.");
}
}
9.3 Context
public class PaymentService
{
private IPaymentStrategy _strategy;
public PaymentService(
IPaymentStrategy strategy)
{
_strategy = strategy;
}
public void ProcessPayment(decimal amount)
{
_strategy.Pay(amount);
}
}
The Context doesn't contain payment-specific logic.
10. Complete C# Console Application
Let's build a complete example.
Step 1 – Strategy Interface
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
Step 2 – Credit Card Strategy
public class CreditCardStrategy
: IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using Credit Card.");
Console.WriteLine(
"Credit Card payment successful.");
}
}
Step 3 – PayPal Strategy
public class PayPalStrategy
: IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using PayPal.");
Console.WriteLine(
"PayPal payment successful.");
}
}
Step 4 – Bank Transfer Strategy
public class BankTransferStrategy
: IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using Bank Transfer.");
Console.WriteLine(
"Bank Transfer payment initiated.");
}
}
Step 5 – Context
public class PaymentService
{
private readonly IPaymentStrategy _strategy;
public PaymentService(
IPaymentStrategy strategy)
{
_strategy = strategy;
}
public void ProcessPayment(decimal amount)
{
_strategy.Pay(amount);
}
}
Step 6 – Program
var creditCardPayment =
new PaymentService(
new CreditCardStrategy());
creditCardPayment.ProcessPayment(250);
var paypalPayment =
new PaymentService(
new PayPalStrategy());
paypalPayment.ProcessPayment(500);
var bankPayment =
new PaymentService(
new BankTransferStrategy());
bankPayment.ProcessPayment(1000);
Output:
Processing $250 using Credit Card.
Credit Card payment successful.
Processing $500 using PayPal.
PayPal payment successful.
Processing $1000 using Bank Transfer.
Bank Transfer payment initiated.
The important point is:
PaymentService
doesn't need to know how each payment algorithm works.
11. Payment Processing Example
A real-world payment application could have:
IPaymentStrategy
|
+-- CreditCardStrategy
+-- DebitCardStrategy
+-- PayPalStrategy
+-- BankTransferStrategy
+-- WalletStrategy
The Context simply executes:
_strategy.Pay(amount);
This makes payment methods interchangeable.
12. Discount Calculation Example
Strategy Pattern is not limited to payments.
Suppose an e-commerce application supports:
Regular Customer
Premium Customer
VIP Customer
Employee
Festival Offer
Each customer type has a different discount calculation.
Create:
public interface IDiscountStrategy
{
decimal CalculateDiscount(decimal amount);
}
Regular customer:
public class RegularDiscountStrategy
: IDiscountStrategy
{
public decimal CalculateDiscount(decimal amount)
{
return amount * 0.05m;
}
}
Premium customer:
public class PremiumDiscountStrategy
: IDiscountStrategy
{
public decimal CalculateDiscount(decimal amount)
{
return amount * 0.10m;
}
}
VIP:
public class VipDiscountStrategy
: IDiscountStrategy
{
public decimal CalculateDiscount(decimal amount)
{
return amount * 0.20m;
}
}
Context:
public class DiscountService
{
private readonly IDiscountStrategy _strategy;
public DiscountService(
IDiscountStrategy strategy)
{
_strategy = strategy;
}
public decimal GetDiscount(decimal amount)
{
return _strategy.CalculateDiscount(amount);
}
}
Usage:
var service =
new DiscountService(
new VipDiscountStrategy());
var discount =
service.GetDiscount(1000);
Console.WriteLine(
$"Discount: ${discount}");
This is much cleaner than:
if (customerType == "Regular")
{
}
else if (customerType == "Premium")
{
}
else if (customerType == "VIP")
{
}
13. ASP.NET Core Implementation
Now let's implement the Strategy Pattern in ASP.NET Core.
Imagine an API:
POST /api/payments
Request:
{
"amount": 500,
"paymentMethod": "CreditCard"
}
The application should select the appropriate strategy.
14. Payment Strategy Interface
public interface IPaymentStrategy
{
string PaymentMethod { get; }
Task ProcessAsync(decimal amount);
}
The PaymentMethod property allows the application to identify each strategy.
15. Credit Card Strategy
public class CreditCardPaymentStrategy
: IPaymentStrategy
{
public string PaymentMethod =>
"CreditCard";
public Task ProcessAsync(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using Credit Card.");
return Task.CompletedTask;
}
}
16. PayPal Strategy
public class PayPalPaymentStrategy
: IPaymentStrategy
{
public string PaymentMethod =>
"PayPal";
public Task ProcessAsync(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using PayPal.");
return Task.CompletedTask;
}
}
17. Bank Transfer Strategy
public class BankTransferPaymentStrategy
: IPaymentStrategy
{
public string PaymentMethod =>
"BankTransfer";
public Task ProcessAsync(decimal amount)
{
Console.WriteLine(
$"Processing ${amount} using Bank Transfer.");
return Task.CompletedTask;
}
}
18. Register Strategies with Dependency Injection
In Program.cs:
builder.Services.AddScoped<
IPaymentStrategy,
CreditCardPaymentStrategy>();
builder.Services.AddScoped<
IPaymentStrategy,
PayPalPaymentStrategy>();
builder.Services.AddScoped<
IPaymentStrategy,
BankTransferPaymentStrategy>();
ASP.NET Core can now inject:
IEnumerable<IPaymentStrategy>
and provide all registered strategies.
19. Payment Service
Instead of putting strategy-selection logic in the controller, create an application service.
public class PaymentService
{
private readonly IEnumerable<IPaymentStrategy>
_strategies;
public PaymentService(
IEnumerable<IPaymentStrategy> strategies)
{
_strategies = strategies;
}
public async Task ProcessAsync(
string paymentMethod,
decimal amount)
{
var strategy = _strategies.FirstOrDefault(
x => x.PaymentMethod.Equals(
paymentMethod,
StringComparison.OrdinalIgnoreCase));
if (strategy == null)
{
throw new InvalidOperationException(
$"Unsupported payment method: {paymentMethod}");
}
await strategy.ProcessAsync(amount);
}
}
Register it:
builder.Services.AddScoped<PaymentService>();
20. Request DTO
public class PaymentRequest
{
public decimal Amount { get; set; }
public string PaymentMethod { get; set; } = string.Empty;
}
21. Controller
[ApiController]
[Route("api/payments")]
public class PaymentsController : ControllerBase
{
private readonly PaymentService _paymentService;
public PaymentsController(
PaymentService paymentService)
{
_paymentService = paymentService;
}
[HttpPost]
public async Task<IActionResult> Process(
PaymentRequest request)
{
await _paymentService.ProcessAsync(
request.PaymentMethod,
request.Amount);
return Ok(new
{
Message = "Payment processed successfully."
});
}
}
Request:
POST /api/payments
{
"amount": 750,
"paymentMethod": "PayPal"
}
The flow becomes:
HTTP Request
↓
PaymentsController
↓
PaymentService
↓
Strategy Selection
↓
PayPalPaymentStrategy
↓
Payment Processing
22. Strategy Factory
When an application has many strategies, selecting the strategy using:
.FirstOrDefault(...)
may eventually become repetitive.
We can introduce a Strategy Factory.
public interface IPaymentStrategyFactory
{
IPaymentStrategy GetStrategy(
string paymentMethod);
}
Implementation:
public class PaymentStrategyFactory
: IPaymentStrategyFactory
{
private readonly IEnumerable<IPaymentStrategy>
_strategies;
public PaymentStrategyFactory(
IEnumerable<IPaymentStrategy> strategies)
{
_strategies = strategies;
}
public IPaymentStrategy GetStrategy(
string paymentMethod)
{
var strategy = _strategies.FirstOrDefault(
x => x.PaymentMethod.Equals(
paymentMethod,
StringComparison.OrdinalIgnoreCase));
return strategy
?? throw new InvalidOperationException(
$"Unsupported payment method: {paymentMethod}");
}
}
Register:
builder.Services.AddScoped<
IPaymentStrategyFactory,
PaymentStrategyFactory>();
Now the service becomes:
public class PaymentService
{
private readonly IPaymentStrategyFactory _factory;
public PaymentService(
IPaymentStrategyFactory factory)
{
_factory = factory;
}
public async Task ProcessAsync(
string paymentMethod,
decimal amount)
{
var strategy =
_factory.GetStrategy(paymentMethod);
await strategy.ProcessAsync(amount);
}
}
23. Strategy Pattern with Dictionary Lookup
For larger systems, a dictionary can also be used.
public class PaymentStrategyFactory
{
private readonly Dictionary<
string,
IPaymentStrategy> _strategies;
public PaymentStrategyFactory(
IEnumerable<IPaymentStrategy> strategies)
{
_strategies = strategies.ToDictionary(
x => x.PaymentMethod,
StringComparer.OrdinalIgnoreCase);
}
public IPaymentStrategy GetStrategy(
string paymentMethod)
{
if (_strategies.TryGetValue(
paymentMethod,
out var strategy))
{
return strategy;
}
throw new InvalidOperationException(
$"Unsupported payment method: {paymentMethod}");
}
}
This can make strategy lookup efficient and centralize selection logic.
24. Strategy Pattern and Open/Closed Principle
One of the major benefits of Strategy is its relationship with the Open/Closed Principle.
Suppose today we support:
CreditCard
PayPal
BankTransfer
Tomorrow the business requests:
Wallet
Without Strategy:
Modify PaymentService
Add another if/else
Test existing logic again
With Strategy:
Create WalletPaymentStrategy
Register it
Existing strategies don't need to change.
This is a strong example of designing software that is:
Open for extension, but closed for modification.
25. Strategy Pattern and Dependency Inversion
The Context depends on:
IPaymentStrategy
rather than:
CreditCardPaymentStrategy
This reduces coupling.
PaymentService
|
↓
IPaymentStrategy
↑
|
CreditCardStrategy
PayPalStrategy
BankTransferStrategy
The Context doesn't care which concrete implementation is being used.
26. Strategy Pattern in Enterprise Applications
Strategy Pattern is particularly useful for business rules.
For example:
Shipping
IShippingStrategy
|
+-- StandardShipping
+-- ExpressShipping
+-- InternationalShipping
+-- SameDayShipping
Tax Calculation
ITaxStrategy
|
+-- DomesticTax
+-- InternationalTax
+-- StateTax
+-- SpecialTax
Pricing
IPricingStrategy
|
+-- RegularPricing
+-- PremiumPricing
+-- WholesalePricing
+-- PromotionalPricing
Notification
INotificationStrategy
|
+-- EmailNotification
+-- SMSNotification
+-- PushNotification
Authentication
IAuthenticationStrategy
|
+-- PasswordAuthentication
+-- OAuthAuthentication
+-- CertificateAuthentication
27. Strategy vs State
This is one of the most important interview questions.
Both patterns use interfaces and interchangeable implementations.
But their purpose is different.
Strategy
Strategy answers:
Which algorithm should I use?
Example:
PaymentService
|
+-- CreditCard
+-- PayPal
+-- BankTransfer
The client can choose a strategy.
State
State answers:
What should this object do based on its current state?
Example:
Order
↓
PendingState
↓
ConfirmedState
↓
ShippedState
The object's behavior changes as its state changes.
Easy way to remember
Strategy = Choose an algorithm
State = Behavior changes with state
28. Strategy vs Command
Strategy
Encapsulates an algorithm.
CalculateDiscount
CalculateTax
ProcessPayment
Command
Encapsulates a request.
CreateOrderCommand
CancelOrderCommand
RefundOrderCommand
Simple distinction
Strategy → HOW something is done
Command → WHAT operation should be performed
29. Strategy vs Template Method
Both can encapsulate algorithms.
Strategy
Uses composition.
Context
↓
Strategy
Template Method
Uses inheritance.
Base Class
↓
Concrete Class
Strategy provides runtime interchangeability.
Template Method defines the skeleton of an algorithm and allows subclasses to customize selected steps.
30. Strategy vs Chain of Responsibility
Strategy
Usually selects one algorithm.
Request
↓
One selected Strategy
Chain of Responsibility
A request can move through multiple handlers.
Request
↓
Handler A
↓
Handler B
↓
Handler C
Strategy answers:
Which algorithm should process this?
Chain of Responsibility answers:
Which handler should handle this request?
31. Strategy vs Simple Factory
These are often confused.
Simple Factory
Responsible for:
Creating an object
Example:
PaymentFactory
↓
Creates PayPalStrategy
Strategy
Responsible for:
Encapsulating behavior/algorithm
They can work together:
Factory
↓
Creates Strategy
↓
Context
↓
Executes Strategy
32. Advantages of Strategy Pattern
1. Eliminates Large Conditional Statements
Instead of:
if
else if
else if
we use independent strategy classes.
2. Supports Open/Closed Principle
New algorithms can be added without modifying the Context.
3. Improves Testability
Each strategy can be unit tested independently.
4. Reduces Coupling
The Context depends on an abstraction.
5. Improves Maintainability
Each business rule has its own class.
6. Runtime Flexibility
Strategies can be selected dynamically.
7. Promotes Single Responsibility
Each strategy focuses on one algorithm.
33. Disadvantages
1. More Classes
A simple conditional can become multiple classes.
2. More Abstraction
Developers need to understand the Strategy interface and Context.
3. Strategy Selection Still Needs Design
Something must determine which strategy to use.
For example:
Factory
Dependency Injection
Dictionary
Configuration
Business Rules
4. Can Be Overengineering
For two trivial conditions, a Strategy Pattern might be unnecessary.
5. Client May Need to Know Strategies
If strategy selection is exposed directly to callers, the client might need knowledge of available strategies.
A Factory or resolver can reduce this coupling.
34. Best Practices
1. Keep Strategies Focused
Each strategy should represent one clear algorithm or business rule.
2. Use Interfaces
Prefer:
IPaymentStrategy
over directly coupling the Context to concrete classes.
3. Use Dependency Injection
ASP.NET Core's built-in DI container works very well with Strategy Pattern.
4. Avoid Giant Strategy Classes
If a strategy contains too many unrelated responsibilities, split it.
5. Centralize Strategy Selection
Use:
Factory
Resolver
Dictionary
Keyed DI
where appropriate.
6. Give Strategies Meaningful Names
Prefer:
CreditCardPaymentStrategy
PremiumCustomerDiscountStrategy
ExpressShippingStrategy
over:
Strategy1
Strategy2
Strategy3
7. Keep the Context Simple
The Context should delegate behavior rather than recreate the algorithms.
8. Validate Strategy Inputs
A strategy should validate inputs relevant to its algorithm.
35. Common Mistakes
Mistake 1 – Strategy Interface Too Broad
Avoid:
public interface IStrategy
{
void Execute();
void Save();
void SendEmail();
void Log();
}
Keep interfaces focused.
Mistake 2 – Context Contains the Same Conditions
If you create Strategy classes but still have:
if (paymentType == "PayPal")
inside the Context, you may not be getting the full benefit of the pattern.
Mistake 3 – Creating Strategies Manually Everywhere
Avoid:
new PayPalStrategy()
throughout the application.
Use Dependency Injection or a Factory where appropriate.
Mistake 4 – Strategy Selection Scattered Across Controllers
Keep strategy selection in an application service, resolver, or factory.
Mistake 5 – Using Strategy for Simple Logic
Not every conditional needs a design pattern.
36. Unit Testing Strategies
Each strategy can be tested independently.
For example:
[Fact]
public void VipDiscount_ShouldCalculateCorrectly()
{
var strategy =
new VipDiscountStrategy();
var discount =
strategy.CalculateDiscount(1000);
Assert.Equal(200, discount);
}
This test focuses only on:
VIP discount calculation
The other strategies can be tested separately.
37. Testing the Context
We can also test whether the Context correctly delegates to the Strategy.
Example:
var strategy =
new CreditCardStrategy();
var service =
new PaymentService(strategy);
service.ProcessPayment(500);
The test verifies that:
PaymentService
↓
CreditCardStrategy
works correctly.
38. Strategy Pattern with Modern ASP.NET Core
Modern ASP.NET Core applications provide several ways to implement Strategy Pattern.
Common approaches include:
Dependency Injection
IEnumerable<T>
Factory
Resolver
Dictionary
Keyed Services
For example, in versions supporting keyed services:
builder.Services.AddKeyedScoped<
IPaymentStrategy,
CreditCardPaymentStrategy>("CreditCard");
builder.Services.AddKeyedScoped<
IPaymentStrategy,
PayPalPaymentStrategy>("PayPal");
Then the appropriate strategy can be resolved using the key.
This can be useful when strategy selection is naturally represented by a stable key.
39. Strategy Pattern in Microservices
Strategy Pattern can be used inside microservices to isolate business rules.
For example:
Order Service
|
+-- Pricing Strategy
|
+-- Tax Strategy
|
+-- Shipping Strategy
An order-processing flow might look like:
Order Request
↓
Order Service
↓
Pricing Strategy
↓
Tax Strategy
↓
Shipping Strategy
↓
Order Total
This keeps each calculation independently replaceable.
40. Strategy Pattern + Factory Pattern
These patterns often work together.
Suppose we have:
PaymentStrategy
and:
PaymentStrategyFactory
The Factory determines:
Which strategy?
The Strategy determines:
How should the payment be processed?
Architecture:
Controller
↓
PaymentService
↓
PaymentStrategyFactory
↓
IPaymentStrategy
↓
Concrete Strategy
This is a very common enterprise design.
41. Strategy Pattern + Dependency Injection
This combination is especially useful in ASP.NET Core.
ASP.NET Core DI Container
|
+-- CreditCardStrategy
+-- PayPalStrategy
+-- BankTransferStrategy
|
↓
PaymentService
Advantages include:
Loose coupling
Easy unit testing
Easy replacement
Centralized configuration
Cleaner application services
42. Strategy Pattern + Configuration
Sometimes the strategy can be selected through configuration.
For example:
{
"Payment": {
"DefaultMethod": "PayPal"
}
}
The application can select:
PayPalPaymentStrategy
without hard-coding the default behavior into the business service.
However, configuration should not replace proper validation and business rules.
43. Real-World Example: Shipping
Suppose an e-commerce platform supports:
Standard
Express
Same Day
International
Define:
public interface IShippingStrategy
{
decimal CalculateCost(
decimal weight,
string destination);
}
Implement:
StandardShippingStrategy
ExpressShippingStrategy
SameDayShippingStrategy
InternationalShippingStrategy
The order service doesn't need to know the shipping formula.
It simply calls:
var cost =
strategy.CalculateCost(
weight,
destination);
44. Real-World Example: Tax Calculation
Tax calculation can differ based on:
Country
State
Customer Type
Product Type
Tax Rules
Instead of:
if (country == "...")
{
}
else if (state == "...")
{
}
we can use:
ITaxStrategy
|
+-- USATaxStrategy
+-- CanadaTaxStrategy
+-- EuropeTaxStrategy
This makes tax rules easier to isolate and test.
45. Real-World Example: File Export
An application may support:
PDF
Excel
CSV
JSON
XML
Strategy:
public interface IExportStrategy
{
Task ExportAsync(
IEnumerable<object> data);
}
Concrete strategies:
PdfExportStrategy
ExcelExportStrategy
CsvExportStrategy
JsonExportStrategy
XmlExportStrategy
The application can choose the appropriate exporter without modifying the main business service.
46. Real-World Example: Notification
Notification systems often support:
Email
SMS
Push Notification
Teams
Webhook
Strategy:
public interface INotificationStrategy
{
Task SendAsync(
string message);
}
Implementations:
EmailNotificationStrategy
SmsNotificationStrategy
PushNotificationStrategy
WebhookNotificationStrategy
The notification service can dynamically select the required implementation.
47. When Should You Use Strategy?
Strategy Pattern is appropriate when:
There are multiple algorithms for the same task.
Business rules change frequently.
Large
if-elseorswitchstatements are growing.Different customers require different rules.
Different payment methods require different processing.
Different shipping methods require different calculations.
You need runtime algorithm selection.
Algorithms should be independently testable.
New algorithms are likely to be added.
48. When Should You Avoid Strategy?
Don't automatically use Strategy when:
There is only one algorithm.
There are only two trivial conditions.
The logic is unlikely to change.
Additional classes would make the code harder to understand.
The abstraction provides little value.
Remember:
A design pattern should solve a real design problem, not simply make the code look more sophisticated.
49. Strategy Pattern – Complete Flow
The complete enterprise flow can look like this:
Client
|
↓
API Controller
|
↓
Application Service
|
↓
Strategy Resolver
|
+---------+---------+
| | |
↓ ↓ ↓
Strategy A Strategy B Strategy C
| | |
+---------+---------+
|
↓
Business Result
The major benefit is that the Context doesn't need to know the internal implementation of each strategy.
50. Strategy Pattern in One Example
Let's summarize with payment processing.
Without Strategy:
public void Pay(
string type,
decimal amount)
{
if (type == "CreditCard")
{
// Logic
}
else if (type == "PayPal")
{
// Logic
}
else if (type == "BankTransfer")
{
// Logic
}
}
With Strategy:
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
Then:
IPaymentStrategy
|
+-- CreditCardPaymentStrategy
+-- PayPalPaymentStrategy
+-- BankTransferPaymentStrategy
Context:
public class PaymentService
{
private readonly IPaymentStrategy _strategy;
public PaymentService(
IPaymentStrategy strategy)
{
_strategy = strategy;
}
public void Pay(decimal amount)
{
_strategy.Pay(amount);
}
}
Now:
PaymentService
↓
IPaymentStrategy
↓
Selected Algorithm
This is the Strategy Pattern.
51. Interview Questions
Beginner Questions
1. What is the Strategy Design Pattern?
It defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable.
2. What problem does Strategy solve?
It eliminates complex conditional logic and allows algorithms to vary independently from the code that uses them.
3. What are the main components?
Context
Strategy
Concrete Strategy
4. What is the Context?
The class that uses a Strategy to perform an operation.
5. What is a Concrete Strategy?
A class containing one implementation of the algorithm defined by the Strategy interface.
Intermediate Questions
6. Strategy vs State?
Strategy chooses an algorithm.
State changes behavior based on the object's current state.
7. Strategy vs Command?
Strategy encapsulates an algorithm.
Command encapsulates a request.
8. Strategy vs Template Method?
Strategy uses composition.
Template Method uses inheritance.
9. How does Strategy support SOLID?
It strongly supports:
Single Responsibility Principle
Open/Closed Principle
Dependency Inversion Principle
10. Can Strategy Pattern be used with Dependency Injection?
Yes. ASP.NET Core DI is particularly well suited to Strategy implementations.
Advanced Questions
11. How would you implement multiple strategies in ASP.NET Core?
Register each implementation with DI and inject:
IEnumerable<IPaymentStrategy>
Then resolve the required strategy using a factory, resolver, dictionary, or another selection mechanism.
12. How would you avoid a large switch when selecting strategies?
Use:
Factory
Resolver
Dictionary
Keyed DI
depending on the application's requirements.
13. Can Strategy Pattern be used with CQRS?
Yes.
For example, a command handler could use a Strategy to select a business algorithm.
14. Can Strategy Pattern and Factory Pattern work together?
Yes.
The Factory creates or resolves the appropriate Strategy, while the Strategy encapsulates the algorithm.
15. What are the disadvantages of Strategy?
The main disadvantages are:
Increased number of classes
Additional abstraction
Strategy selection complexity
Potential overengineering
16. When would you choose Strategy instead of a switch?
Choose Strategy when the conditional branches contain substantial, independently changing algorithms or business rules.
A small, stable switch can remain simpler.
17. Can a Strategy maintain state?
It can, but strategies are often easier to reason about when they are stateless.
If state itself determines behavior, consider whether the State Pattern is more appropriate.
18. How do you unit test Strategy Pattern?
Test each Concrete Strategy independently, then test the Context or service to verify that the correct Strategy is selected and invoked.
19. Is Strategy Pattern useful in microservices?
Yes. It is useful for isolating business rules such as pricing, taxation, shipping, routing, payment processing, and notification algorithms.
20. What is the biggest sign that Strategy Pattern is needed?
A growing class with many conditional branches implementing different algorithms is a strong signal.
52. Key Takeaways
Remember these points:
1. Strategy is a Behavioral Design Pattern
It focuses on interchangeable algorithms and business rules.
2. Strategy removes complex conditional logic
Instead of:
if/else
switch
we use:
Strategy classes
3. Each strategy represents one algorithm
CreditCardStrategy
PayPalStrategy
BankTransferStrategy
4. The Context uses the Strategy
Context
↓
Strategy
5. Strategy supports Open/Closed Principle
Add new strategies without modifying existing algorithms.
6. Dependency Injection works extremely well with Strategy
ASP.NET Core makes Strategy registration and resolution straightforward.
7. Strategy and State are different
Strategy → Choose algorithm
State → Behavior changes based on state
8. Factory and Strategy can work together
Factory → Select/resolve strategy
Strategy → Execute algorithm
Conclusion
The Strategy Design Pattern is one of the most practical behavioral patterns for modern C# and ASP.NET Core applications.
Whenever you encounter code like:
if (type == "A")
{
// Algorithm A
}
else if (type == "B")
{
// Algorithm B
}
else if (type == "C")
{
// Algorithm C
}
ask yourself:
Are these different algorithms that should be independently maintained and tested?
If the answer is yes, the Strategy Pattern may be a good fit.
The architecture becomes:
Context
|
↓
IStrategy
|
+-----------+-----------+
| | |
↓ ↓ ↓
Strategy A Strategy B Strategy C
Instead of putting every algorithm into one large class, each algorithm gets its own focused implementation.
In ASP.NET Core, the combination of:
Strategy Pattern
+
Dependency Injection
+
Factory/Resolver
can produce a clean, flexible, and maintainable architecture for complex business rules.
The key lesson is:
Use Strategy when you have multiple interchangeable algorithms or business rules and want to select the appropriate one without tightly coupling the client to their implementations.
🚀 Coming Up Next: Part 4.10 – Template Method Design Pattern
In the next article, we'll explore the Template Method Design Pattern, including:
What is the Template Method Pattern?
Why do we need it?
Template Method vs Strategy
Algorithm skeleton concept
Primitive operations
Hook methods
UML Class Diagram
Complete C# Console Application
Data processing example
Payment processing example
ASP.NET Core implementation
Abstract classes and inheritance
Template Method with dependency injection
Real-world enterprise examples
Advantages and disadvantages
Best practices
Common mistakes
Template Method vs Strategy
Template Method vs Factory Method
Template Method vs State
Interview questions
The Template Method Pattern will demonstrate how to define the overall structure of an algorithm in a base class while allowing derived classes to customize specific steps.

