#begin
This is the fourth blog in the series about the SOLID principles of Object Oriented Design. It is all about the I in SOLID, the Interface Segregation Principle. If you missed the first three blogs in this series, you can read them here, here and here.
The Interface Segregation Principle
I learned about the Interface Segregation Principle (ISP) a couple of years ago while watching some of Uncle Bob’s lectures on youtube. He has multiple lectures about the SOLID principles, yet there is not a single one where he finds the time to cover them all. He always ends up only talking about three out of five principles or something. I fondly remember some guy in the audience asking if he could tell something about the ISP during the section where the audience can ask questions.
But what does the ISP mean? Well it does not have a nicely formally defined explanation like some of the other SOLID principles but it means something along the lines of this:
No client should be forced to depend on methods it does not use.[1]
This principle was invented by Uncle Bob himself while he was consulting at Xerox. The story goes that these printers were driven by objects called jobs, that all implemented some interface which defined ALL possible functions of the printer itself. So each command would know the function header of all the other ones. Uncle Bob suggested that these functions are all useless and should be removed. So he essentially made use of what we now know as the Command Pattern. This allowed them to encapsulate all logic needed in a simple “Execute()” function, and each job only needed to know about this one function.
So, Uncle bob invented the ISP to counteract what he calls “Fat” or “Polluted” interfaces [2]. These are interfaces that appear to hold (many) unrelated functions and thus, are not specific to a single client. These should be separated into multiple nice, cohesive interfaces. This way, when you decide to implement some interface you are not forced to implement many unnecessary functions. Which, in the end, will lead to unwanted couplings between your code.
I think we all have some experience with this where we need to implement some interface in order to be able to hook into some system, and we have to leave in these stubbed functions, without any implementation, just because the ISP is violated. This is bad interface design! When you read this principle it seems perfectly reasonable and logical to design interfaces this way. Yet I’m pretty sure we can al think of some interface we are using in our daily job where we have stubbed out functions, just for the sake of implementing one (or more) function(s) of an interface we need.
So what would be an example of a violation of the ISP? Well let’s try to reproduce the issue Uncle Bob ran into at Xerox. Imagine we have some object that represents a job for the printer to execute. To me, the concept of a “job” represents some independent use-case that has to be executed, and most likely, in a multi-threaded manner. This job should only care about the data and functions it needs to reach it’s end goal.
The diagram above would be a perfect example of how NOT to implement such printer jobs. Now, you might be thinking exactly the same as I do, like who would ever implement it this way? No offense but, the one who originally wrote it like this, really did miss some of the early CS classes in school. Maybe this is because the CS field in universities was still kinda new, I do not know.
The changes Uncle bob made to the diagram above look something like this:
Of course, this looks way better. Now each job has an isolated and encapsulated “Execute” function where you can implement the details (there will probably be many more private functions on these jobs). This is the kind of class I think of when I talk about “Jobs” or “Commands”. (When I implement some system using Uncle Bob’s Clean Architecture, my Interactors (use-cases) look very similar as well)
Another example
This was such an obvious example that I think I need to give a proper one. So let’s take a simple example I have seen many times in game development. A Door. Let’s imagine an interface like this.
It has Five functions, Open, Close, IsOpen, Lock and IsLocked. Now, this seems perfectly reasonable but there is a semantic error here. Not all doors can be locked, and there may even be doors that cannot be opened or closed. I have seen this multiple times in games where people would just leave the Lock and IsLocked functions stubbed out. What we preferably want is to separate the door interface from the locking interface and create two interfaces. Then, you will end up with something like this:
You might event want to remove the inheritance from lockable door to Door if you do not want to override the Open and Close functions. And you could take it another step further, and add a third interface where you combine the IDoor and ILockable interfaces into an ILockableDoor interface.
Impact on Software Architecture
The ISP on software architecture is similar as the impact of the Liskov Substitution Principle (as described in the previous blog). When we raise the level of abstraction to the component level and we want to (re)use substitutable components as much as we can, we need to take the ISP seriously. Without it you will end up with components that need to implement too many unrelated functions.
Yet, there is a more interesting “issue” with the ISP, and that has everything to do with the type of language you are using. The ISP is only a “problem” in statically typed languages… In dynamically typed languages you can just write your source file and call the function and it will be inferred at run-time. This means in languages like C# or Java you would get a compile error, but in Python or Ruby you would not and it would work anyway.
This is also why systems written in dynamically typed languages are often more flexible and less tightly coupled.
Impact on other paradigms
In regard to other paradigms I can’t find much information that correlates with the ISP. And, that is probably a good thing. I do not think the ISP is that much of a problem in the functional domain since, again, it is all about function composition and not inheritance trees. With a functional language I think you will end with more flexible systems naturally. It’s just the way these languages work.
Conclusion
This was a short blog, as I expected. The ISP is a simple principle, yet very important when you are building large systems that need to run for a long time. When you design your interfaces badly they will most likely accumulate more and more functions over time, leading towards the problem Uncle Bob encountered at Xerox. So always try to keep interfaces as small and cohesive as possible.
#end
01010010 01110101 01100010 01100101 01101110
References
[1] ISP definition – Agile Software Development: Principles, Patterns, and Practices. – Robert C. Martin.
[2] C++ Report – https://drive.google.com/file/d/0BwhCYaYDn8EgOTViYjJhYzMtMzYxMC00MzFjLWJjMzYtOGJiMDc5N2JkYmJi/view
Recent Comments