SOLID Principles – Open/Closed Principle

Software entities should be open for extension, but closed for modification.

First time I’ve read this principle my initial react was “WHAT”, are these people high or something? I failed to realize how big difference is between introducing code trough extensions and through modifications. I knew about object oriented programming, abstractions, interfaces but looking back at the way I was coding then, I didn’t make use of the power behind these constructs at their full potential.

Let’s use the PaymentCalcuator example from out Single Responsibility post, there we concluded that how the calculation is done for each is a responsibility on its own. We can think of the following requirement for the payment calculation.

  • 10% discount for VIP customers
  • 10% discount in October
  • 5% for items from which they ordered at least 50

This I pretty simple you might say, so the code will look something like:

class PaymentCalculator
{
	double Calculate(Order order)
	{
		double total = 0;
		foreach(var item in order.Items)
		{
			var price = item.Quantity * item.IndividualPrice;
			if (item.Quantity >= 50)
				price *= 0.95;
			total += price;
		}
		if (order.ClientInfo.IsVIP)
			total *= 0.9;
		if (DateTime.Now.Month == 10)
			total *= 0.9;

		return total;
	}
}

All goes well until somebody informs us that there is a new requirement. The client wants to add a shipping fee for every order which must be shipped further away than 20 km. At this point you probably have you face in your hands, while you realize the spider web of code, that will be implemented in this function, if this goes on like this. But fear not, this is where we need to come up with a better system.

My interpretation of this principle is the following “I should be able to fulfill new requirements by extending existing classes instead of modifying them”, inheriting a different class from the PaymentCalculator will not be of much use because once a requirement goes away or is added is hard if the linkage between them was done by inheritance. So let’s invent the concept of PaymentCalculationModifier. Let’s implement our 4 calculation into this manner and let’s see where we end up.

interface IPaymentCalcModifier
{
	double Calculate(Order order, double currentPrice);
}

class BasePriceCalculator : IPaymentCalcModifier
{
	public double Calculate(Order order, double currentPrice)
	{
		return order.Items
			.Sum(x => x.TotalPrice);
	}
}

class QuantityBasedModifier : IPaymentCalcModifier
{
	public double Calculate(Order order, double currentPrice)
	{
		return currentPrice - order.Items
			.Where(x => x.Quantity >= 50)
			.Sum(x => x.TotalPrice * 0.05);
	}
}

class MonthBasedModifier : IPaymentCalcModifier
{
	public double Calculate(Order order, double currentPrice)
	{
		return DateTime.Now.Month == 10
			? currentPrice * 0.90
			: currentPrice;
	}
}

class VipStatusBasedModifier : IPaymentCalcModifier
{
	public double Calculate(Order order, double currentPrice)
	{
		return order.ClientInfo.IsVIP
			? currentPrice * 0.90
			: currentPrice;
	}
}

class PaymentCalculator
{
	public double Calculate(Order order, 
           IEnumerable<IPaymentCalcModifier> modifiers)
	{
		double total = 0;
		foreach (var modifier in modifiers)
		{
			total = modifier.Calculate(order, total);
		}

		return total;
	}
}

void UpdatePrice(Order order)
{
	new PaymentCalculator().Calculate(order, 
		new IPaymentCalcModifier[]
		{
			new BasePriceCalculator(),
			new QuantityBasedModifier(),
			new VipStatusBasedModifier(),
			new MonthBasedModifier()
		});
}

I know that the above code has it’s difficulties as well but its a lot better system. Any time we need to add functionality that modifies the way the amount is calculated, we can add that through implementing the interface and adding it to the calculation modifiers. Probably we won’t need to touch the PaymentCalculator any time soon.

This principle can be very useful when you know that a number of things affect one part of the code, and you feel that the list isn’t full, or you expect changes happening throughout the development phase.

Be careful to not invent any further requirement to justify the need of these kind of constructs (it can lead to endless “what if” talks and over engineering). I follow the fool me once rule. I implement a simple system like the first example if i know there is only one thing the influences the calculations, but later on if we find another, i assume that there will be more and then i do the necessary refactoring. Other times you know from the start that one area in the application is subject to change a lot, then it’s a good idea to start with an extensible system.