C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
108: The Strategy Pattern
Listen, we've all been there. You're building a feature, and it starts off simple. You have one or two ways of doing something, so a quick if/else or a switch statement feels like the right move. It's fast, it's readable, and it works. But then the requirements change. Your boss wants a third option, then a fourth, and suddenly your method is a 200-line monster that's terrifying to touch because one small change might break three other things.
That's where the Strategy Pattern comes in. Instead of one class trying to handle every possible variation of an algorithm, we pull those variations out into their own separate classes. We define a common interface, and the main class just calls that interface without caring which specific version is actually doing the work.
The trap of the growing switch statement
Let's say we're building a shipping calculator for an e-commerce site. I'll start the way I usually do when I'm rushing—which is exactly the mistake I want you to avoid. I'll just put everything in one class.
public class ShippingCalculator
{
public double CalculateShipping(double weight, string carrier)
{
if (carrier == "FedEx")
{
return weight * 1.5;
}
else if (carrier == "UPS")
{
return weight * 1.2;
}
else if (carrier == "DHL")
{
return weight * 2.0;
}
throw new Exception("Carrier not supported");
}
}
At first glance, this is fine. But imagine we add 10 more carriers. We have to keep modifying this specific method, which violates the Open/Closed Principle (classes should be open for extension, but closed for modification). Every time I add a new carrier, I risk breaking the logic for the existing ones. It's a maintenance nightmare waiting to happen.
Defining the common interface
To fix this, I need to stop the ShippingCalculator from worrying about how the shipping is calculated. It should only care that a calculation happens. So, I'll create an interface that defines what a shipping strategy looks like.
public interface IShippingStrategy
{
double Calculate(double weight);
}
Now, any carrier we support must implement this interface. This separates the "what" from the "how".
Plugging in the specific carriers
Now I can move the actual logic into its own dedicated class for each carrier. This is where the "Strategy" part actually happens. Each class is a different strategy for calculating the cost.
public class FedExStrategy : IShippingStrategy
{
public double Calculate(double weight) => weight * 1.5;
}
public class UpsStrategy : IShippingStrategy
{
public double Calculate(double weight) => weight * 1.2;
}
public class DhlStrategy : IShippingStrategy
{
public double Calculate(double weight) => weight * 2.0;
}
I love this approach because if I need to change how FedEx calculates their rates, I go exactly to the FedExStrategy class. I don't have to scroll through a giant switch statement and pray I didn't accidentally delete a curly brace in the UPS section.
Letting the client decide the strategy
Finally, I'll rewrite the ShippingCalculator. Instead of taking a string and guessing what to do, it will now take an IShippingStrategy. I'll call this the "context" in pattern terms.
public class ShippingCalculator
{
private IShippingStrategy _strategy;
// I'll use a constructor to set the strategy,
// but you could also use a method to change it at runtime.
public ShippingCalculator(IShippingStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(IShippingStrategy strategy)
{
_strategy = strategy;
}
public double Calculate(double weight)
{
return _strategy.Calculate(weight);
}
}
Now, look at how clean the usage is. The calculator doesn't know which carrier is being used; it just trusts the strategy to do its job:
var fedex = new FedExStrategy();
var calc = new ShippingCalculator(fedex);
Console.WriteLine(calc.Calculate(10.0)); // 15.0
// Oh, the user changed their mind? Just swap the strategy.
calc.SetStrategy(new UpsStrategy());
Console.WriteLine(calc.Calculate(10.0)); // 12.0
By decoupling the algorithm from the class that uses it, we've made the code infinitely more scalable. If you want to add a "DroneDeliveryStrategy", you just create a new class. You don't touch a single line of existing, working code.
📋 Practical Task
Implementing a Discount Strategy for a Loyalty Program
You are building a checkout system for a store. Currently, the store has three types of customers: Regular (no discount), Silver (10% discount), and Gold (20% discount).
Your task is to implement the Strategy Pattern to handle these discounts. Avoid using if/else or switch statements to determine the discount amount.
- Create an
IDiscountStrategyinterface with a methoddouble ApplyDiscount(double totalAmount). - Implement three strategy classes:
RegularDiscount,SilverDiscount, andGoldDiscount. - Create a
ShoppingCartclass that accepts anIDiscountStrategyand uses it to calculate the final price of the cart. - In your main program, demonstrate the functionality by creating a cart with a total of $100 and applying both a Silver discount and then a Gold discount.
There are no comments for now.