In my latest posts I introduced the Single Responsibility Principle, the Open Closed Principle and the Liskov Substitution Principle of SOLID. Now it is time to see the Interface Segregation Principle (ISP). In Object Oriented Programming (OOP) abstraction is a valuable asset especially with interfaces that allow you to design your application by contracts. Even if the interfaces do not contain any actual code it is important to control their size. The following sentence defines the ISP rule:
Clients should not be forced to depend on methods they do not use.
In other words it means that you should have small dedicated interfaces instead of larger ones. This way the client code will only have access to the functionalities it needs. I have created an example to show how the ISP can be violated and how to fix it.
public interface IRepository<T> where T : class { void Insert(T entity); void Update(T entity); T Get(int id); void Delete(int id); }
I have this interface that represents a repository to manipulate the entities stored in my database, it has the basic CRUD (Create Read Update Delete) operations. I will use this interface on my User entity:
public class User { public int Id { get; set; } // other properties } public class UserRepository : IRepository<User> { public void Insert(User entity) { // save user in DB } public void Update(User entity) { // update user in DB } public User Get(int id) { // retrieve user from DB return new User(); } public void Delete(int id) { // delete user from DB } }
Good, I have now a full control over my User objects lifetime in the database. Now I also want to be able to retrieve some event logs to be able to consult them, I will use my repository interface to do so.
public class EventLog { public int Id { get; set; } public string Message { get; set; } // other properties } public class LogRepository : IRepository<EventLog> { public void Insert(EventLog entity) { // nothing to do } public void Update(EventLog entity) { // nothing to do } public EventLog Get(int id) { // retrieve log from DB return new EventLog(); } public void Delete(int id) { // nothing to do } }
The issue is that my logs are stored in the database by other applications and in the one I’m working on I don’t have any logging to do. This force me to leave 3 methods empty since they are not used but are defined in my interface… This is clearly a violation of the Interface Segregation Principle. In this example I have a repository that is “read & write” and another that is just “read”. My abstraction is not correct in my context.
I have to find a solution that allows me to keep the functionalities for both repositories without breaking the ISP. I will then create two separate interfaces:
public interface IReadRepository<T> where T : class { T Get(int id); } public interface IWriteRepository<T> where T : class { void Insert(T entity); void Update(T entity); void Delete(int id); }
As you can see one is dedicated to “read” operations and the other to “write” operations. My LogRepository can now inherits the IReadRepository because it does not need anything else.
public class LogRepository : IReadRepository<EventLog> { public EventLog Get(int id) { // retrieve log from data source return new EventLog(); } }
And what about the UserRepository? Since it is “read & write” it will implement both interfaces. Multiple inheritance is commonly use when the ISP is in play.
public class UserRepository : IReadRepository<User>, IWriteRepository<User> { public void Insert(User entity) { // save user in DB } public void Update(User entity) { // update user in DB } public User Get(int id) { // retrieve user from DB return new User(); } public void Delete(int id) { // delete user from DB } }
With time my application is likely to grow and use more and more entities from the database that will need “read” and “write” operations. If it is the case I can create an IReadWriteRepository that will be defined like this:
public interface IReadWriteRepository<T> : IReadRepository<T>, IWriteRepository<T> where T : class { }
In my example the UserRepository can implement this interface since it works as an alias over the two other ones. The ISP does not prevent you from regrouping interfaces under a common one. This will allow your code to be “cleaner” and explicit without losing functionalities.
This is the end of the Interface Segregation Principle presentation, remember to look for partial implementations of an interface if you want to spot where the ISP is violated.
I hope you liked this 4th principle of the SOLID series and as always do not hesitate to share, comment and give your opinion.
See you next time!
This is not an example of a violation of the ISP. The ISP isn’t about implementing interfaces that contain more than the concrete class is prepared to define. That just means you’re implementing the wrong interface (possibly a violation of the Single Responsibility Principle).
The ISP is violated when the code that has a reference to an object implementing a specific interface doesn’t use many of the methods on that interface. So if you have some code with a reference to an IRepository, but it’s only calling Get(), then you have a violation of the ISP. This would be true even if somehow the LogRepository was able to implement Insert(), Update(), and Delete(). It’s not about classes implementing the interface, it’s about how many of the methods on the interfaces are actually being used by another module (the “client”).
Robert Martin, the author of the ISP, tweeted this:
“Imagine a stack class with both push and pop. Imagine a client that only pushes. If that client depends upon the stack interface, it depends upon pop, which it does not need.”
LikeLike