I just came from a trip to Spain (which I will probably post about next week) and I have exactly one day to write this piece before my scheduled release. Since I’m rushing, might as well go all the way. Here’s the TL;DR version of this post:
ISP stands for Interface Segregation Principle, and it’s the I in SOLID OOP. This principle simply says that clients – meaning your classes – should not be forced to depend on interfaces they don’t use. If you have a group of methods defined in an interface, and you find that a class you’re creating needs to use some – but not all – of those methods, then you probably need to split that interface instead of forcing your new class to use it. This makes your code more organized, and just makes more sense.
For example, from our last post if we somehow defined an ICanAct interface with the methods “act” and “eat” (because most actors eat), then this interface would apply to James and Nadine, but not to my butt. We should split this interface to something like ICanAct or IActor with the method “act”, and ICanEat or IEater with the method “eat”. James and Nadine will implement both interfaces, while my butt would implement only IActor.
That’s it. You know what ISP stands for.
What are you still doing here?
Oh well, since you’re here anyway, might as well do a purely hypothetical mental experiment to explore it.
We’ve mentioned abstractions and interfaces before, but to recap, an interface defines what a class can do, not what that class is, nor how it should do it. In our previous example, the IActor interface defines an action, “act”, and if you see anyone bearing the mark of IActor on its forehead, you will definitely know that it can do some form of acting.
As you build your application, you will find that you sometimes add more and more methods to an already existing interface, and then something changes and you’re forced to reconsider. Let’s say we’re coding a game, or a simulation of a society, where law enforcement is necessary. What do law enforcers need to do? Well, here’s a list of what you might code:
You would expect that a law enforcer like a policeman or policewoman would investigate possible crimes to come up with suspects. They can chase after suspects, and then apprehend and prosecute them according to the law. If the suspects resist, they should disable them. As a last resort, they can kill if these suspects pose lethal danger to the officers or innocent bystanders. Pretty reasonable, right?
If you code the law enforcers, you should also code the behavior of the people who can be suspects. Here’s how you might go about it:
Everyone has the potential to commit a crime (but note that just because they can doesn’t mean they did). Regardless of his guilt, if someone were to become a suspect and officers come to arrest him, he may choose to surrender or he may choose to run. If he runs and the officers chase him, he may choose to fight to disable the officers, or kill them, in order get away.
The specific details of why and how the police and suspects will do these things are not our concern at this point. As I said, interfaces just define what something can do.
Now, what would happen if the requirements for the simulation changed, and you had to model a government running a ruthless war against crime, like a campaign against drugs? Let’s see how the behaviors may evolve.
First, the behavior of law enforcement may be different. For one, they may be motivated to act quickly on tips against potential drug suspects and do away with investigation. You streamline your code in such a way that police act immediately on information they get. If this is the case, your original interface will now violate ISP since law enforcement would no longer bother with investigating crimes. Also, since this is an all-out war on drugs, with zero tolerance whatsoever, the directive to law enforcers is to use lethal force at the slightest hint of a threat. Disabling suspects now becomes a deprecated method.
[NOTE] Please replace “Suspect” with “IOffender” for every graphic moving forward. I do these in paint and am too lazy to edit. Just deal with it.
It’s fine. If the law enforcers commit any excesses, IInvestigators can investigate them anyway, and the system will still work.
What would happen to suspect behavior if this were the case? The initial reaction would probably be “shit I don’t want to get fucking murdered”, and so whether or not they’re guilty, if they’ve been pointed out as a suspect, they will probably choose to surrender.You might want to deprecate fight and kill methods.
What about running, though? What if they then realize that surrendering will land them in a very overcrowded jail. Maybe they’ll be one 4000 inmates packed in a space made for 800. Maybe they would wait ages to see their loved ones for simple visitation privileges. How might they logically act, then? I guess we’re keeping the run method, because running away is not very threatening to law enforcement, right? They shouldn’t shoot to kill a suspect who runs.
But how can a law enforcer react? Review the current version of the ILawEnforcer interface.
If you code it this way and the suspect runs to evade apprehension and prosecution, what else can a law enforcer do? Is killing fine? Here I’ll dive into implementation details a bit and veer away from ISP. Maybe you code your specific policemen (who implement ILawEnforcer) to kill if lethal opposition is present, and so they just have to say that the suspects were in possession of guns or something. In any case, you still have the IInvestigator interface in your back pocket. You can still implement an “investigate” method for crimes.
Now that we think about it, why are we limiting the scope of our ILawEnforcer interface to policemen and policewomen? Ordinary citizens also have a responsibility to uphold the law, right? Therefore, concerned citizens should also be able to do law enforcement to a certain extent.
So going back to ISP, ordinary citizens have no training in apprehending and prosecuting suspects. They don’t have the access to book criminals for their offenses, and throw them in jail. If that’s the case, we need to segregate methods once more so that ordinary citizens don’t have to depend on an interface with methods they don’t use.
We added two new interfaces, IProsecutor which can be implemented by anyone who can apprehend and prosecute, and IPoliceInformer which can be implemented by the ordinary citizens to point the police towards possible suspects. Police and ordinary citizens therefore implement only those interfaces that are relevant to them, making your code easier to maintain, read, and execute.
[NOTE] Legally, apprehension resides in the domain of law enforcers, and not prosecutors. IProsecutor is just a name for the interface in our model.
If this is the landscape of interfaces you have, here are some questions to ask:
- Who implements the IInvestigator interface? Who watches the watchers?
- Given that police and ordinary citizens can both be offenders, and can also both be law enforcers, can ordinary citizens really enforce the law if they were up against the police?
- If the default action for law enforcement is to kill, how long before you need to change the IOffender interface again so that their methods say “kill” instead of “run”?
- If the ILawEnforcer interface only holds a “chase” and “kill” method, can you really call someone who implements it a law enforcer?