Clean&Reactoring Entry Created: 05 Feb 2026 Updated: 05 Feb 2026

refactoring with interfaces ( if-else )


In this example, the decision path is hardcoded. The ShippingCalculator knows exactly how every shipping method works. If you want to add "Overnight Shipping," you must modify this class, risking bugs in existing logic.

public class ShippingCalculator
{
public decimal CalculateCost(string deliveryType, decimal weight)
{
// BAD: Hardcoded decision path (Early Binding)
// If we add a new type, we must modify this class (Violating OCP).
if (deliveryType == "Standard")
{
return weight * 5m;
}
else if (deliveryType == "Express")
{
return weight * 10m + 20m; // Higher base cost
}
else if (deliveryType == "International")
{
return weight * 20m + 50m; // Customs fee
}
else
{
throw new ArgumentException("Unknown delivery type");
}
}
}

✅ Good Example: Late Binding (Adheres to OCP)

Here, we replace the if-else control structure with Polymorphism (specifically the Strategy Pattern). The ShippingContext does not know how the calculation is done; it only knows that it can be done via the interface.

To add "Overnight Shipping," you simply create a new class. No existing code is touched.

1. Define the Contract

public interface IShippingStrategy
{
decimal Calculate(decimal weight);
}

2. Implement Strategies

public class StandardShipping : IShippingStrategy
{
public decimal Calculate(decimal weight) => weight * 5m;
}

public class ExpressShipping : IShippingStrategy
{
public decimal Calculate(decimal weight) => weight * 10m + 20m;
}

public class InternationalShipping : IShippingStrategy
{
public decimal Calculate(decimal weight) => weight * 20m + 50m;
}

3. The Consumer (Late Binding)

public class ShippingContext
{
private readonly IShippingStrategy _strategy;

// Dependency Injection allows us to swap logic at runtime
public ShippingContext(IShippingStrategy strategy)
{
_strategy = strategy;
}

public decimal ExecuteCalculation(decimal weight)
{
// GOOD: The decision is delegated to the object implementation.
// The context doesn't care which concrete type it is.
return _strategy.Calculate(weight);
}
}

Handling the "Exception" (External Data)

As you noted, you eventually need to convert the raw string (from the DB or API) into the Strategy. This is the Factory layer, which is the valid place for the "decision" logic because it handles the I/O boundary.

public static class ShippingStrategyFactory
{
// A Dictionary is often cleaner than a switch/if-else block here,
// effectively acting as a lookup table.
private static readonly Dictionary<string, IShippingStrategy> _strategies = new()
{
{ "Standard", new StandardShipping() },
{ "Express", new ExpressShipping() },
{ "International", new InternationalShipping() }
};

public static IShippingStrategy GetStrategy(string deliveryType)
{
return _strategies.TryGetValue(deliveryType, out var strategy)
? strategy
: throw new ArgumentException("Invalid type");
}
}
Share this lesson: