In this series we are looking at the SOLID design principles. In this week’s tip, we’ll cover the fourth principle in the acronym: the Interface Segregation principle (LSP).
If you missed the previous posts in this sequence, you can find them here:
- SOLID Design Principles Part 1 – Single Responsibility Principle
- SOLID Design Principles Part 2 – Open-Closed Principle
- SOLID Design Principles Part 3 – Liskov Substitution Principle
The Interface Segregation Principle
“Many small specific interfaces are better than one large general-purpose interface”.
The dependency of one class to another should depend on the smallest possible interface. The purpose is to make clients use the smallest and most cohesive interface as possible.
Most clients of a class only need a small number of the methods of an interface. To facilitate this, we should create separate, focused interfaces, each of which serves a specific purpose. A class will then implement only the interfaces it needs, as opposed to having to implement many methods that it doesn’t need. Implementing a large general-purpose interface often leads to accidental close class couplings and additional dependencies.
Extending the ISP to its logical conclusion, the most specific and focused interface will consist of only one single method to implement. This is often called a SAM (Single Abstract Method) interface. In the Java API, it is now called a functional interface. A functional interface has a number of additional uses with lambdas in functional programming. See my previous blog post on functional interfaces for more information.
Java API Examples
There are a large number of obvious examples of the ISP in the standard Java API. Just in the java.lang
package we can find the Appendable
, AutoCloseable
, CharSequence
, Comparable
, Iterable
, Readable
and Runnable
interfaces. All of these interfaces have very focused, narrow uses. Most are functional interfaces containing only a single abstract method.
Two of the previous interfaces are not functional interfaces, but they are still highly focused on specific use cases. Appendable
has three overloaded append()
methods to append characters to the end of other objects. CharSequence
defines six common methods that provide read-only access to objects that contain sequences of char
s.
Custom Interface Example
Let’s look at how we would implement ISP in our own design. Let’s say we’re modelling an Employee
class and a Contractor
class. Obviously they have similar fields: name
, surname
, dateOfBirth
, gender
, etc. They have differences too: employees receive a salary, while contractors have an hourly rate that determines their remuneration. They have similar methods: work()
, doTimeSheets()
, drinkCoffee()
, etc. The common fields and methods can be factored out and moved to a Person
base class. The work()
and doTimeSheets()
methods obviously can’t be moved to the Person
class because they have nothing to do with a Person
.
A good idea would be to move the work()
and doTimeSheets()
methods into an interface that would be appropriately implemented in the Employee
and Contractor
classes.
public interface Productive { public void work(); public void doTimeSheets(); }
We should debate whether doing time sheets is actually being productive, and whether the method belongs in the Productive
interface. Let’s think of a Gardener
or a Guard
class that inherits from Employee
and implements Productive
. No one can argue that they work, but no one would expect them to fill in time sheets.
We might also want to model a GuardDog
to patrol with the Guard
. Again, no one would argue with the fact that the GuardDog
is a working dog, and should have a work()
method. And very importantly , a GuardDog
would never fill in a time sheet!
Obviously putting the doTimeSheets()
method in the Productive
interface limits its use, and doesn’t model Productive
objects correctly. Doing so could lead to unintended class dependencies.
What now? ISP to the rescue! Let’s apply the Interface Segregation principle to our current design and split the Productive
interface into two focused interfaces, each with their own use cases:
public interface Productive { public void work(); } public interface CompleteAdminstrativeTasks { public void doTimeSheets(); }
This would lead to code which follows good design principles by conforming to the ISP:
public class Employee extends Person implements Productive, CompleteAdminstrativeTasks { public void work() {} // appropriate code here... } public void doTimeSheets() {} // appropriate code here... } } public class GuardDog extends Dog implements Productive { public void work() {} // appropriate code here... } }
What’s next?
Next week we’ll look at the final principle in the acronym: the Dependency Inversion Principle (DIP).
As always, please share your comments and questions.