Interface Segregation Principle.
16 Aug 2017We have heard about this design principle in software development. Let’s get straight into a problem and try and understand what this principle says!
Let us design an ATM (Automated Teller Machine).
An ATM has transactions. A transaction can be WithdrawalTransaction, DepositTransaction, TransferTransaction etc.
Next, we have a UI for ATM, and UI can be Braille UI, Screen UI and Speech UI.
Different transactions
can speak to UI methods to publish their message, however, the message might differ based on a transaction.
Having a single UI interface having RequestTransferAmount
, RequestDepositAmount
that can work with all the types
wouldn’t be the right solution here. It is because, a type of transaction, say DepositTransaction, can enforce a change to UI interface to add a new functionality, which would further lead to
changes in all the other transactions that extended the same UI. In short our the problem is:
“A change in UI for solving a problem withDepositTransaction
resulted in changes with TransferTransaction
.
It looks like we need a UI for Withdraw, Deposit and Transfer.i.e, DepositUI, WithdrawUI and TransferUI.
First stage of Solution:
- All Transaction classes extend Transactions as expected.
- We have separate DepositUI, WithdrawUI and TransferUI that gets mixed into
UI
. - A particular transaction won’t work with
UI
since it is already fatty. (We call such an interface fatty because not all of these functions would be relevant for a particular implementation. That is,TransferTransaction
would have to provide a fake/dummy/empty implementation forRequestDepositAmount
if it is extending thisfatty
interface) - All transactions can then be provided with its corresponding
UI
type as given below
Intent of doing this, clients should not be forced to depend on interfaces they don’t use. This is called interface segragation principle
So, here we go: A part of the solution:
This is it!
A few more points to note
You may note that, DepositTransaction must know about DepositUI and Withdraw Transaction must know about WithDrawUI. As you can see, we solved this problem by making the constructor of each type of transaction expecting the right type of UI. We could pass in the UI as given below
Another way of handling this problem is to have a global package listing down all types of UIs. i.e, static global objects. Hence we can avoid passing in the UI during the construction of different transactions. However, they are two different approaches that allow us to follow Interface Segregation Principle. Please note that if these globals are put into a class instead of a package, that would be violation of the principle as we are in a way combining all the interfaces together when we import this class to work with a specified transaction. Personally, I would go with the solution given in the above example, that is, pass the right UI to the right transaction.
The polyad and monad - Monadic is not always the best approach
Assume that, We might be in need of a function f
that has to access both DepositUI and
WithDrawUI. We could separately pass these UIs to f as given below.
We made the function polyadic, and passed multiple parameters into it.
Let me quote a sentence from the book Clean Code
written by Robert.C.Martin here
A function with two arguments is harder to understand than a monadic function. For example, writeField(name) is easier to understand than writeField(output-stream, name). Dyads aren’t evil, and you will certainly have to write them. However, you should be aware that they come at a cost and should take advantage of what mechanism may be available to you to convert them into monads.
In the above example, you might have noted that we are mixing in various UIs to have a UI type. Following the above quotes let us make our function monadic. (You may note that UI in our example is a mix of all interfaces)
Here we made it monadic, however it comes with cost - it drifted us away from following our interface segregation principle. That is, any change in any of the interface affects the function f and all of its clients forcing it to recompile. Hence we prefer polyadic approach here..