Introduction
The Liskov Substitution Principle (LSP) is the “L” in the SOLID principles and a cornerstone of robust object-oriented design.
Definition: Subtypes must be substitutable for their base types.
This means that objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program.
Violations of LSP lead to broken inheritance hierarchies and brittle code. In this article, we’ll explore how to apply LSP correctly using real-world Java examples.
1. The Basic Idea
Consider this example:
class Bird {
void fly() {
System.out.println("Flying");
}
}
class Sparrow extends Bird { }
class Ostrich extends Bird { }
If we write:
Bird bird = new Ostrich();
bird.fly(); // ❌ Problem: ostriches can't fly
We’re violating the Liskov Substitution Principle because substituting Ostrich
breaks the expected behavior.
2. Fixing the Hierarchy
We can restructure our design:
interface Bird { }
interface FlyingBird extends Bird {
void fly();
}
class Sparrow implements FlyingBird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich implements Bird {
// No fly() method
}
Now, we can use FlyingBird
where flying behavior is expected, and Bird
when it’s not assumed.
3. Real-World Example: Document Exporters
Suppose we build a document exporting tool:
abstract class DocumentExporter {
abstract void export(String content);
}
Now we create:
class PdfExporter extends DocumentExporter {
public void export(String content) {
System.out.println("Exporting to PDF: " + content);
}
}
class ReadOnlyExporter extends DocumentExporter {
public void export(String content) {
throw new UnsupportedOperationException("Read-only documents can't be exported.");
}
}
This violates LSP: calling export()
on ReadOnlyExporter
breaks the expected contract.
4. Correcting the Design
Refactor with a clearer interface contract:
interface Exportable {
void export(String content);
}
class PdfExporter implements Exportable {
public void export(String content) {
System.out.println("Exporting to PDF: " + content);
}
}
class ReadOnlyDocument {
// No export functionality
}
Now we’ve ensured that Exportable
truly means “can be exported,” without tricking consumers into catching exceptions.
5. When You See LSP Violations
- Subclasses throw exceptions in overridden methods
- Subclass behavior contradicts superclass expectations
- Tests break when substituting child for parent
Conclusion
Following the Liskov Substitution Principle ensures your inheritance structures are logical, safe, and predictable. It’s not just about compiling — it’s about trust between classes.
You can find the complete code of this article here in GitHub.
📚 Related: Interface Segregation Principle in Java