A look at interfaces and how to write clean and maintainable code with SOLID principle 4/5.
In this post we look at the I- in the SOLID acronym which stands for the interface segregation principle. We start with a definition:
Clients should not be forced to depend on methods that they do not use.
Source: Agile Software Development; Robert C. Martin;
Basically, the principle is similar to the single responsibility principle in the sense that classes should only implement methods that they actually use.
Imagine we have a subclass that implements all methods in a base class. But for one particular method, the function body raises an exception since we don't want that method to be callable at all. That one particular method would go against the behavioral definition of the class and technically shouldn't be implemented. This will be a violation of the ISP principle and would require a refactor.
You might be thinking why should the sub class implement a method it doesn't need? You see, this principle deals with interfaces. When an interface is inherited all the methods present must be implemented.
Let's take a quick look at interfaces and duck typing before proceeding further.
Interfaces, abstract classes and duck typing in Python
Interfaces are a software construct that define and enforce what methods an object of a class should have. In Python we make use of duck typing to create an interface. We simply use an abstract class to represent an interface. Duck typing states that:
If it walks like a duck and it quacks like a duck, then it must be a duck
Source: Duck typing - Wikipedia
In other words if the class we define behaves like an interface we can use it as if it is an interface. Don't worry if you don't grasp this fully. The key point here is that we are treating our abstract class as an interface (by having empty method bodies and declaring methods abstract, we'll see this later).
Here's a duck for you 🦆
Enough about ducks. Time for some code 👩🏾💻
An interface can be considered to be a fully abstract class. Therefore, we annotate all methods in the abstract class with
Once again we reuse the scenario of the previous post and consider a Car 🙂
We define an abstract base class
Car . Again this can be considered to be an interface since all three methods are defined as abstract.
Next, we create two subclasses namely
SportsCar. These two classes inherit the methods defined in
Car. However notice that a
RegularCar does not have turbo so we raise an exception if a call to
turboAccelerate is made on a
We can test the code in the
__main__ function and see that a call to
turboAccelerate indeed raises the exception. Everything else works as expected.
Now you may be thinking why define the
turboAccelerate function for a
RegularCar if it is not to be used anyway. Exactly! This is the principle of ISP. We should not force a
RegularCar to implement
turboAccelerate if it is not supported. In essence this is a design flaw in our model of the car.
Also, the second issue is that of code maintenance. Imagine that we need to change something in the
turboAccelerate function. Suppose we need to include an extra parameter to the function definition. If we make this change to the interface we also have to make the change in
RegularCar even though the function just throws an exception! In other words, we are forced to make useless changes just to make the code compile.
The fix is pretty easy. I am sure you can already see it 😎
Here is a refactor of the code:
So. Our goal was to remove
turboAccelerate from a
RegularCar. But we can't do that since the interface contains the method and all methods in an interface must be implemented by a subclass. Therefore, the solution is to make two interfaces. One called
NoTurbo and the other called
WithTurbo. We simply make
turboAccelerate a function in
WithTurbo and not in
NoTurbo. A regular car implements
NoTurbo while a turbo car implements
You may have noticed though that we did not exactly create two separate interfaces. Instead we extended
WithTurbo (inheritance). This was done to simply reduce code redundancy since there is only a single extra function in
Great! As you can see now,
SportsCar don't implement any useless functions they don't require.
In this post you learned about the interface segregation principle. Basically the principle states that a class should not be forced to implement functions that it does not use.
As with all SOLID principles, ISP contributes to making code more manageable and robust. This is particularly in the case where a change has to be made to an interface which is propagated downwards to all subclasses implementing it. Without ISP, the function being updated would have to be updated in the subclasses too even though they don't implement any business logic.
That's it for this post. Hope you learned something worthwhile!
Next week, we will reach the end and look at the last of the SOLID principles.
Enjoyed this article? Please consider subscribing using the form below😇
It is 100% free. You get an email every time I post (about once a week) + access to premium content in the future. Connect with me on LinkedIn or follow on Twitter.