Finding Inner Peace With the Liskov Substitution Principle
LSP says that functions that use references to base classes must be able to use objects of derived classes without knowing it. Adhering to it will spare you a headache.
Join the DZone community and get the full member experience.
Join For FreeWe’re successfully handling divergent requirements by conforming to SRP. We’ve made our systems extensible by unleashing the power of OCP. Everything at WooMinus seems calm. But your life couldn’t be further from that. What happened?! What does this have to do with Liskov Substitution Principle? Read on to see how it will bring peace to your life.
King Benedictus and Real Politics
It’s late in the evening. Your wife asked you to go to the shop and buy a liter of milk and, if there are eggs, to get ten. So, you’re coming back carrying 10 liters of milk, the night appears so peaceful. Suddenly, a black van drives by and stops. Two big guys in hoodies punch you and force you in the car. It’s all black. A strangely familiar voice begins to whisper…
Listen carefully. I am King Benedictus. I hired a team of programmers known as the Black Dragons to write a system to control the government. Those suckers claimed it’s open for extension and similar nonsense. Yet, when I asked another team to add support for bribing different kinds of politicians, things started to break unexpectedly. I want you to find the issue and resolve it. You’ve got one hour. You fix the problem on time, and I’ll let you go and make sure you’re safe. You don’t? Pow!
Racing Against Time
You take the laptop and begin to search for the issue. The most prominent domain class in the system is Politician:
public class Politician {
public void bribe(double dollars) {
if(dollars < 2000)
throw new HowDareYouException();
changeMindAboutX();
}
protected void changeMindAboutX() {
// stuff
}
}
This must be the class that the other team tried to extend in order to add new types of politicians! You click the button to get to the subclasses and…
public class CorruptiblePolitician extends Politician {
@Override
public void bribe(double dollars) {
if (dollars < 3000)
throw new HowDareYouException();
changeMindAboutY();
}
private void changeMindAboutY() {
// stuff
}
}
That’s weird. A corruptible politician who wants more money than a regular one? Also, he changes his mind about something completely different. This is a smell. Let’s look at Politician ‘s clients!
public class GovernmentService {
private static final double JUST_ENOUGH_TO_FORCE_X = 2000;
private PoliticianRepository politicianRepository;
// stuff
public void forceX() {
for (Politician politician : politicianRepository.findElected()) {
politician.bribe(JUST_ENOUGH_TO_FORCE_X);
}
}
}
Aha! That explains everything!
Liskov Substitution Principle
The example above is a classic violation of the Liskov Substitution Principle:
Functions that use references to base classes must be able to use objects of derived classes without knowing it.
The person implementing GovermentService reasonably assumed that it’s enough to pay a politician 2000 dollars to make him change his mind about topic X. By adding a new type of politician to the system, the developers made the assumption invalid without fixing the broken code.
LSP and Conditions
Another way to state LSP, which I personally find much more intuitive, uses the terms usually associated with Design by Contract:
…when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
If you look at our example from this perspective, the LSP violation should be very clear:
- A precondition of the dollars argument being at least 2000 has been replaced by a stronger one requiring 3000.
- A postcondition of the politician having changed his mind about X has been replaced by a weaker one of not changing mind about it.
To support this with a code example, let’s take a look at a different extension of Politician:
public class CorruptiblePolitician extends Politician {
@Override
public void bribe(double dollars) {
if (dollars < 1500)
throw new HowDareYouException();
changeMindAboutX();
changeMindAboutY();
}
private void changeMindAboutY() {
// stuff
}
}
This one is perfectly valid. Is it enough to pay the guy 2000? It is! Will he change his mind about X? He will! You get the point.
Restoring Your Inner Peace
Now, that you know the ins and outs of LSP, you’re ready to save your life. King Benedictus claims that the system worked before adding the new type of politician. It’s also true, that the corruptible politicians will change their mind about topic Y when paid at least 3000 dollars. Let’s make the solution take the best from the old and the new:
public class CorruptiblePolitician extends Politician {
@Override
public void bribe(double dollars) {
if (dollars < 2000)
throw new HowDareYouException();
changeMindAboutX();
if (dollars >= 3000)
changeMindAboutY();
}
private void changeMindAboutY() {
// stuff
}
}
Here, the method works with the GovermentService implementation and keeps the new benefits in place. You’re safe!
One More Thing…
Before I let you go. If GovermentService didn’t exist or it used some smarter way of determining the bribe amount, the described problems wouldn’t surface and we probably would not talk about an LSP violation – we could talk about a potential one at most. This is a key characteristic of this principle – everything depends on client’s expectations.
You probably know JDK methods like Arrays.asList() and Collections.unmodifiableXxx(). If you want to be hypercorrect, these do not violate LSP, because interfaces are not types and the rule originally was about subtyping. But when you look at client’s expectations, they rarely expect a list that does not support modifying its contents. Therefore, unless it’s completely clear for everyone that the collection is not modifiable, opt for the more evident immutable collections from Guava.
Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments