SOLID principles
intermediateLiskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without altering correctness.
If code works with a class, it should still work when you hand it a subclass. That is LSP.
A USB port promises power and data. Any USB device should work in any USB port without changing the host's expectations. A device that says "I'm USB" but only works upside down on Wednesdays violates the contract.
Key Concepts
1
The compiler already checks the obvious part: the subclass has the right methods with the right
parameters. LSP is about the part the compiler cannot check — whether the subclass behaves the way
callers expect.
2
The classic example. A square is a rectangle in mathematics, so people write class Square extends
Rectangle. It compiles. Then this code runs:
class Square extends
Rectangle
3
void resize(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
// area should now be 20
}4
Pass it a Square and the area is 16, because setting the height also changed the width. The method did
nothing wrong. The subclass broke a promise the parent made: width and height move independently.
Square
5
No amount of care inside Square fixes this. The relationship itself is wrong.
Square
6
The rules a subclass must follow.
Do not demand more than the parent did.
Parent accepts any positive number.
Subclass rejects anything over 100. -> callers now break7
Do not promise less than the parent did.
Parent's add() always adds the item.
Subclass silently ignores long items. -> callers now break8
Keep the parent's guarantees true.9
The everyday version. You implement an interface and half the methods throw
UnsupportedOperationException. A read-only list that refuses add. A repository that refuses save.
Callers now have to know which concrete class they are holding, which is exactly what interfaces are
supposed to prevent. Java's own Collections.unmodifiableList does this — a useful thing to admit, because
it shows you are describing the rule rather than defending it.
UnsupportedOperationExceptionaddsaveCollections.unmodifiableList
10
How to catch it. Write your tests against the interface, not the class. Then run the same tests against
every implementation. A subclass that fails its parent's tests is breaking LSP, and now you have a failure
instead of an argument.
11
The usual fix is not inheritance. If you are overriding methods to switch them off, the relationship
was never "is a kind of". Use composition: hold the other object as a field and expose only what makes
sense.
12
What the interviewer is probing.1. "What is LSP?" Probing: behaviour versus signatures. Stalls: "A subclass should work like the
parent." Moves up: the three rules — do not demand more, do not promise less, keep the guarantees.
13
2. "Why is Square extends Rectangle a problem?" Probing: the classic case. Stalls: "A square is a
rectangle." Moves up: true in maths, false in behaviour, because the setters stop being independent.
14
3. "Is throwing UnsupportedOperationException a violation?" Probing: honesty. Stalls: "No, the JDK
does it." Moves up: yes, and the JDK does it as a trade-off — worth naming rather than defending.
15
4. "How would you find violations?" Probing: method. Stalls: "Code review." Moves up: one test
suite written against the interface, run against every implementation.
16
5. "What is the fix?" Probing: the alternative. Stalls: "Override more carefully." Moves up:
composition — the inheritance itself is the bug.
17
The contract a subtype must honour18
Preconditions may be WEAKER (accept more), never stronger
Postconditions may be STRONGER (guarantee more), never weaker
Invariants must be preserved
Exceptions no new checked exceptions the caller cannot
expect19
Square / Rectangle, concretely20
void resize(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20; // holds for Rectangle
} // fails for Square: area == 1621
Square cannot satisfy this while keeping its own invariant
(width == height). The types are not substitutable, whatever
geometry says.22
Everyday violations23
class ReadOnlyRepo implements Repository {
public void save(T t) {
throw new UnsupportedOperationException();
}
}
-> callers must now know the concrete type. Fix: split the
interface (ReadableRepo / WritableRepo) -- which is ISP
doing LSP's work.24
class FastList extends ArrayList<String> {
@Override public boolean add(String s) {
if (s.length() > 10) return false; // silently drops
return super.add(s);
}
}
-> weakened postcondition: callers expect add to have added.25
The mechanical check
Write the test suite against the INTERFACE, run it against every
implementation. A subtype that fails the parent's tests is an
LSP violation with a stack trace attached.