Hello! Today, we shall discuss the elusive Interface Segregation Principle (ISP). The dry definition of this principle is as follows: Clients should not be forced to depend on interfaces they do not use.
In platypus language, this is how we can make sense of it:
“Are you very sure all the child classes need this behaviour?”
This is almost like the opposite of Liskov Substitution Principle: In LSP, we question if the child should change the behaviour it inherits from the parent; However, in ISP, we question if the parent should make all of its children inherit or implement some irrelevant or useless behaviour!
Just some context before we look at today’s code.
- Dr Doofenshmirtz is Agent P’s (Perry) nemesis.
- Agent T has his own nemesis.
- Therefore, only Agent P should annoy Dr Doofenshmirtz.
Now, we are ready to dive into the code!
interface Agent {
foilEvilPlans();
annoyDoofenshmirtz();
}
class AgentP implements Agent {
@Override
foilEvilPlans() {
print("Evil plans foiled!");
}
@Override
annoyDoofenshmirtz() {
print("Hehehehe");
}
}
class AgentT implements Agent {
@Override
foilEvilPlans() {
print("Evil plans foiled!");
}
@Override
annoyDoofenshmirtz() {
print("Error: Not suppose to annoy Doof.");
}
}
In the code above, we have an interface Agent
, and two agents AgentP
and AgentT
implementing this Agent
interface that defines some behaviour an agent should have.
On closer inspection, we see that we unknowingly made Agent T annoy Dr. Doofenshmirtz, even though Doof is Perry’s nemesis! Clearly, not all child classes need to annoy Doof… Can we do better? Yes! We segregate the interface by splitting it up!
interface Agent {
foilEvilPlans();
}
interface Perry {
annoyDoofenshmirtz();
}
class AgentP implements Agent, Perry {
@Override
foilEvilPlans() {
print("Evil plans foiled!");
}
@Override
annoyDoofenshmirtz() {
print("Hehehehe");
}
}
class AgentT implements Agent {
@Override
foilEvilPlans() {
print("Evil plans foiled!");
}
}
Bea-utiful! Now, Agent T can do its job without needing to learn the irrelevant skill of annoying Doof, while Perry can continue to kick Doof’s ass!
Consistent with Single Responsibility Principle
From the example above, we see that we can gather all the relevant methods and properties under a single interface, while splitting up methods that obviously do not belong together. We know that annoying Dr. Doofenshmirtz is a Perry thing to do and not really an Agent thing. Therefore, by segregating the interfaces into multiple interfaces that do different things, we would inevitably ensure that these interfaces follow the Single Responsibility Principle.
From this, we learn that the ISP is just a more specific case of the SRP, but it deserves its placing as one of the SOLID principles because splitting interfaces is a strangely useful technique to ensure your code becomes more reusable and less fragile.
“Less fragile?”, you might ask. Yes! Imagine that you are using the lousy code we have above (before the interface segregation), with the addition that you also implement the annoyDoofenshmirtz
function in a parent class so that all agents can inherit it. What if your function is buggy? Then all agents would be inheriting that buggy function, and everybody would become buggy. No-go!
How to follow the Interface Segregation Principle
Like the analytical Agent P during missions, we have to plan how we write our code.
- Gather all the methods and attributes that are related together
- Separate code that are unrelated to each other.
- You will then get some subgroups with distinct behaviours (that should not overlap. If they do, then they belong in the same subgroup).
- Split these subgroups up into multiple interfaces (or classes)!
- There you have it!
Have fun!