Inheritance Without Betrayal
Inheritance is powerful—but with great power comes… well, you know the rest.
Ever swapped in a subclass and everything broke quietly? That’s a violation of the Liskov Substitution Principle (LSP)—the "L" in SOLID.
Objects of a superclass should be replaceable with objects of its subclasses without breaking the application. — Barbara Liskov
You miss 100% of the principles you ignore. — Me, channeling Michael Scott” — Sathish Kumar Saravanan
Let’s unpack what that really means, using some straightforward C# examples and a healthy dose of practicality.
What’s the Big Idea?
When you inherit from a base class, you're making a promise:
You can use me anywhere you'd use the base class, and things will still work as expected.
The moment a subclass starts throwing exceptions, skipping behavior, or changing meaning—you’ve broken that promise. Your design is fragile, even if the compiler says it’s fine.
Let’s look at a real-world example.
A Violation in Disguise
Imagine you’re building a system that handles documents:
public class Document
{
public virtual void Print()
{
Console.WriteLine("Printing document...");
}
}
Now let’s say we want to support digital-only documents:
public class DigitalDocument : Document
{
public override void Print()
{
throw new NotSupportedException("Digital documents can't be printed.");
}
}
Uh oh.
Sure, it compiles. But if someone uses DigitalDocument
in a place expecting a Document
, things break.
That’s exactly what LSP warns us about.
✅ Refactor to Respect LSP
Instead of forcing everything into a single hierarchy, let’s define capabilities more explicitly.
public interface IPrintable
{
void Print();
}
public class PaperDocument : IPrintable
{
public void Print()
{
Console.WriteLine("Printing paper document...");
}
}
public class DigitalDocument
{
public void Open()
{
Console.WriteLine("Opening digital document...");
}
}
Now DigitalDocument
never pretends to be printable. And PaperDocument
cleanly implements what it can actually do.
No surprise exceptions. No broken expectations. Everyone’s behaving themselves.
Why This Matters in the Real World
Violating LSP leads to:
- 🚨 Unexpected runtime errors
- 🧪 Tests that pass for base classes but fail for subclasses
- 🧱 Code that becomes harder to extend safely
Respecting LSP keeps your inheritance trees clean and your codebase honest. Subclasses don’t have to do everything, but they shouldn’t pretend to either.
A Quick LSP Check
Ask yourself:
- Can I pass this subclass wherever the base class is expected… and still trust the behavior?
- Does this subclass silently remove or alter functionality?
- Would a user of the base class be surprised by this subclass?
If the answer’s “uh… maybe,” it’s time to refactor.
Final Thoughts
The Liskov Substitution Principle is really about predictability.
Inheritance is meant to give you power, not headaches. When you design subclasses that follow LSP, your systems become safer, more understandable, and easier to refactor.
Don’t build traps disguised as child classes. Build confidence. Build clarity.
If your subclass breaks the rules of the base class, it’s not inheritance—it’s sabotage.
Top comments (0)