Interface Segregation Principle - Explained with an amazing example.
You might have read different definitions of the 'Interface Segregation Principle' online. It is one of the principles of SOLID.
Before we go into any definition, let's understand why do we need the ISP(Interface Segregation Principle) and what problem does it solve.
We will explore some code related to bank instruments, precisely Savings Account, Current Accounts, Fixed Deposits, Tax Saving Deposits, Term Loans, Education Loans. Most of you will be aware of these terms but if you're not, a quick google search about these terms should be enough.
Let's start by imagining a situation. A bank wants to build an app for bank customers. This app should be able to display all account and fixed deposit details and should support various operations like 'withdraw', 'deposit', 'pay a loan's EMI'.
Let's look at the initial class design for various instruments in this application code.
Here is the diagram:-
Here is the code for the same: -
This is the interface that all bank instruments will be implementing.
Let's look at the class for the most used bank instrument - 'Savings Account'.
We won't be implementing any business logic, we will only simulate the working with print statements.
As you might notice, 2 methods viz. getMaturityPeriod
and payEMI
have to implemented with an empty implementation. This does not look good because any new developer who wants to add or improve any functionality will be clueless when he/she notices the two methods.
Let's look at few more instruments in this version of the code:-
The current account class looks similar to the Savings Account one and has the same problems. But then why did we place the getMaturityPeriod
method in our Account interface? Let's look at a class which has the implementation for this method.
The fixed deposit class has a valid implementation for the getMaturityPeriod
method, and so does another class Tax Saving Fixed Deposit.
They have implemented one of the two unimplemented methods in Savings Account class, but what about the second one? The payEMI
method is unimplemented in Saving and Current Account as well as in Fixed Deposit classes, then why did we keep that method in Account
interface? Because we have a class who has the implementation for that method.
The Education Loan Account has the valid implementation for the payEMI
method. So does another class Term Loan Account.
But, notice that these two methods have an empty implementation for getMaturityPeriod
and deposit
methods. So we are now facing the same previous problem just with one different method deposit
. But, how is an empty implementation harmful?
Let's look at a use case in for end users in which it is.
If any account holder wants to deposit money in his/her savings account, and on the user accounts page he/she can see the deposit option for Loan Accounts also, this not only confuses the user but makes the application look like it was lazily developed.
What if a user clicks on the Get Maturity Period option on a Savings Account component, it won't result in any action as we don't have an implementation for the same. This will bloat the UI with non-working buttons and makes the User Experience not so good.
Let's look at a developer use case where empty implementations can be harmful.
Let's say that, a new bank instrument needs to be added and it can only implement one of the methods in the Account
interface, then it would have to provide an empty implementation for the rest of the methods. Any employee who is new to the codebase would have no idea why so many methods has to implemented with no valid code.
Another critical harm is that if a new functionalities are to be added by a new employee for a loan account called 'Foreclosure' or 'Settlement' without adding new interfaces, then the two new methods will have to added in the Account
interface then implemented in respective Loan Accounts, this does not look like a problem. But what about other classes who are implementing the same Account
interfaces. The new team member's local build will be broken due to compile time issues and he/she will have to again add empty implementation of the new Loan functionalities to all the other classes. This could turn out to be a nightmare when there are even more classes and when more functionalities are added. So what's the solution?
The problem here is that classes are forced to implement a method just because they are implementing the same interface.
Let's look into the solution now.
Here is the diagram: -
We have an account interface again but with very minimal methods.
We will also add a few more interfaces.
Notice which interface is extending another interface, this will give you an idea about the methods that each interface contains.
Here are the same classes which we had looked at previously.
The SavingsAccount
class does not need to implement any unnecessary methods now.
Same is the case with Current Account.
Here's how the Fixed Deposit classes look.
It implements the getMaturityPeriod
method but does not need to implement payEMI
unlike the previous code. This is because we only implement the interfaces for which a class has a valid implementation. So is the case for Tax Saving FD.
Now going towards the Loan classes.
The Education Loan class does not need to provide any empty implementations. So is the case for the Term Loan class.
The Term Loan does not need to implement isPartiallyWithdraw
method as it does not support that functionality.
And now, if any developer wants to add that functionality to the Term Loan class, he/she has to just implement the IPartialWithdraw
interface.
If a new functionality needs to added for Fixed Deposits, then the IFixedDeposit
interface needs to change and implementations should be added only for those classes which implement IFixedDeposit
.
We can see that we have overcome all our problems that we faced in the previous code.
This was possible because we segregated the interfaces well. Hence, no class was forced to implement unnecessary methods. Every class implemented the interfaces it required.
Thus, the principle we used to solve is called the 'Interface Segregation Principle'.
We can thus define ISP such as:-
If a piece of code has an interface that contains many generic methods and its sub-classes are forced to implement the unnecessary methods with an empty implementation or forced to throw an exception, we have violated the Interface Segregation Principle.
I know the article might seem very long for a single topic, but that's how I feel concepts should be articulated. Let me know if you liked this one!