This article is part of a series on the SOLID design principles. You can start here or jump around using the links below!
S – Single Responsibility
O – Open/Closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion
The interface segregation principle can be a bit subjective at times, but the most common definition you will find out there is :
No client should be forced to depend on methods it does not use
In simple terms, if you implement an interface in C# and have to throw NotImplementedExceptions you are probably doing something wrong.
While the above is the “definition”, what you will generally see this principle described as is “create multiple smaller interfaces instead of one large interface”. This is still correct, but that’s more of a means to achieve Interface Segregation rather than the principle itself.
Interface Segregation Principle In Practice
Let’s look at an example. If we can imagine that I have an interface called IRepository, and from this we are reading and writing from a database. For some data however, we are reading from a local XML file. This file is uneditable and so we can’t write to it (essentially readonly).
We still want to share an interface however because at some point we will move the data in the XML file to a database table, so it would be great if at that point we could just swap the XML repository for a database repository.
Our code is going to look a bit like this :
interface IRepository { void WriteData(object data); object ReadData(); } public class DatabaseRepository : IRepository { public object ReadData() { //Go to the database and read data return new object(); } public void WriteData(object data) { //Go to the database and write data } } public class XmlFileRepository : IRepository { public object ReadData() { //Go to the database and read data return new object(); } public void WriteData(object data) { //Don't allow a user to write data to the XML Repository. throw new NotImplementedException(); } }
Makes sense. But as we can see for our XMLFileRepository, because we don’t allow writing, we have to throw an exception (An alternative would to just have an empty method which is probably even worse). Clearly we are violating the Interface Segregation Principle because we are implementing an interface whose methods we don’t actually implement. How could we implement this more efficiently?
interface IReadOnlyRepository { object ReadData(); } interface IRepository : IReadOnlyRepository { void WriteData(object data); } public class DatabaseRepository : IRepository { public object ReadData() { //Go to the database and read data return new object(); } public void WriteData(object data) { //Go to the database and write data } } public class XmlFileRepository : IReadOnlyRepository { public object ReadData() { //Go to the database and read data return new object(); } }
In this example we have moved the reading portion of a repository to it’s own interface, and let the IRepository interface inherit from it. This means that any IRepository can also be cast and made into an IReadOnlyRepository. So if we swap out the XmlFileRepository for a DatabaseRepository, everything will run as per normal.
We can even inject in the DatabaseRepository into classes that are looking for IReadOnlyRepository. Very nice!
Avoiding Inheritance Traps
Some people prefer to not use interface inheritance as this can get messy fast, and instead have your concrete class implement multiple smaller interfaces :
interface IReadRepository { object ReadData(); } interface IWriteRepository { void WriteData(object data); } public class DatabaseRepository : IReadRepository, IWriteRepository { public object ReadData() { //Go to the database and read data return new object(); } public void WriteData(object data) { //Go to the database and write data } }
This means you don’t get tangled up with a long line of inheritance, but it does have issues when you have a caller that wants to both read *and* write :
public class MyService { //What if this service also writes? I have to inject the write repository as well? public MyService(IReadRepository readRepository, IWriteRepository writeRepository) { } }
Ultimately it’s up to you but it’s just important to remember that these are just a means to an end. As long as a class is not forced to implement a method it doesn’t care about, then you are abiding by the Interface Segregation Principle.
What’s Next
Next is our final article in this series, the D in SOLID. Dependency Inversion.
You could actually fix the issue by not specifying two repositories with a generic method like this:
Could you create a complete GIST of this example?