SOLID Principles – Interface Segregation Principle

many client-specific interfaces are better than one general-purpose interface.

During interviews when we talk about these principles, candidates try to glide over this one very quickly. People usually understand this principle, and as a result it is considered to be easier to digest even by junior developers. Imagine the surprise on their face, when I ask them “why?”, “convince me that I should respect this principle”. I’m interested in their reasoning to discover their level of understanding. Like the other principles, you will understand it better if you try to not respect it.

In out last example the GeoPaymentModifier was selecting the closest warehouse. Because the closest might not have the ordered good, this can become a more complex task. The responsibility of the GeoPaymentModifier should be to know what amount to charge not to know how to select the best candidate warehouse.

To raise a bit the difficulty, let’s also consider that the warehouse info and their stock is stored in the database. The database abstraction will look something like this:

interface IDataStorageService
{
	IQueryable<UserInfo> Users { get; }
	IQueryable<ClientInformation> Clients { get; }
	IQueryable<Order> Orders { get; }
	IQueryable<WarehouseInfo> Warehouses { get; }

	void CreateOrder(Order order);
}

We also implement a WarehouseSelector that we will give to the GeoPaymentModifier instead of the list of warehouses.

class WarehouseSelector
{
	private readonly IDataStorageService _dataService;

	public WarehouseSelector(IDataStorageService dataService)
	{
		_dataService = dataService;
	}

	public WarehouseInfo SelectWarehouse(Order order)
	{
		return _dataService.Warehouses.First(); // add filter
	}
}

Simple right? Well it seem like the WarehouseSelector can access a lot more data from the database then it should. It could even create orders.

I mentioned in the SRP post that developers can have a hard time deciding where to write the code. Therefore you need to write your code in a defensive manner, exposing just the needed information to the classes you write. We will have to split the IDataStorageService into chunks. I will do this for the WarehouseInfo only, to save some valuable screen space.

interface IDataStorageService : IWarehouseRepository
{
	IQueryable<UserInfo> Users { get; }
	IQueryable<ClientInformation> Clients { get; }
	IQueryable<Order> Orders { get; }

	void CreateOrder(Order order);
}

interface IWarehouseRepository
{
	IQueryable<WarehouseInfo> Warehouses { get; }
}

class WarehouseSelector
{
	private readonly IWarehouseRepository _dataService;

	public WarehouseSelector(IWarehouseRepository dataService)
	{
		_dataService = dataService;
	}

	public WarehouseInfo SelectWarehouse(Order order)
	{
		return _dataService.Warehouses.First(); // add filter
	}
}

class GeoPaymentModifier : IPaymentCalcModifier
{
	private readonly WarehouseSelector _warehouseSelector;

	public GeoPaymentModifier(WarehouseSelector warehouseSelector)
	{
		_warehouseSelector = warehouseSelector;
	}

	public double Calculate(Order order, double currentPrice)
	{
		int shippingLoc = order.ShippingInfo.Location;
		int warehouseLoc = _warehouseSelector
			.SelectWarehouse(order).Location;
		var dist = Math.Abs(shippingLoc - warehouseLoc);

		return dist > 20
			? currentPrice + 100
			: currentPrice;
	}
}

Having well defined abstractions certainly helps the implementing classes also. You probably found yourself in need of implementing an interface, but half of the functions you didn’t know how. Me too, in that case the interface probably wasn’t well written. If you were tempted to solve this by some trickery, you could have been easily in violation with the LSP.

Hope this was enough info to give you a deeper understanding of the ISP. See you soon!