Inheritance was introduced to promote reuse.
But in modern systems,
it is one of the biggest sources of fragility.
Used correctly, it models specialization.
Used incorrectly, it creates tight coupling, broken abstractions, and subtle bugs.
Inheritance is powerful.
And dangerous.
A Brief Historical Context
Inheritance emerged strongly in Smalltalk and later C++ as a way to:
- Reuse behavior
- Model taxonomies
- Represent “is-a” relationships
But as systems scaled, developers discovered something:
Inheritance couples classes more tightly than composition.
The child class is structurally bound to the parent.
If the parent changes, everything below it may break.
What Inheritance Really Means
Inheritance is:
- A specialization relationship
- A behavioral extension mechanism
- A polymorphic substitution mechanism
Inheritance is not:
- A code reuse shortcut
- A way to share utilities
- A dumping ground for common methods
If your only goal is reuse,
inheritance is often the wrong tool.
❌ The Wrong Way: Inheritance for Reuse
class FileLogger
{
protected function format(string $message): string
{
return date('Y-m-d') . ' - ' . $message;
}
}
Now someone wants email logging:
class EmailLogger extends FileLogger
{
public function send(string $message): void
{
$formatted = $this->format($message);
// send email
}
}
Why is this problematic?
- EmailLogger depends on FileLogger
- FileLogger was never meant to be a base abstraction
- Shared logic forces structural coupling
- Changing FileLogger impacts EmailLogger
This is inheritance for reuse.
And it's fragile.
The Rectangle / Square Problem
A classic example.
class Rectangle
{
protected int $width;
protected int $height;
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function area(): int
{
return $this->width * $this->height;
}
}
Now:
class Square extends Rectangle
{
public function setWidth(int $width): void
{
$this->width = $width;
$this->height = $width;
}
public function setHeight(int $height): void
{
$this->width = $height;
$this->height = $height;
}
}
Seems logical.
But watch this:
function resize(Rectangle $rectangle): int
{
$rectangle->setWidth(5);
$rectangle->setHeight(10);
return $rectangle->area();
}
Expected result: 50.
If we pass Square?
Result: 100.
This violates the Liskov Substitution Principle.
The child cannot safely substitute the parent.
Inheritance broke behavioral expectations.
The Real Rule of Inheritance
For inheritance to be valid:
The child must fully respect the behavioral contract of the parent.
Not just the method signatures.
The behavior.
If overriding changes expectations,
inheritance is wrong.
When Inheritance Is Appropriate
Inheritance works well for:
- Framework base classes (with strict contracts)
- Template Method pattern
- Controlled extensibility
- Internal engine hierarchies
Example:
abstract class ReportGenerator
{
public function generate(): void
{
$data = $this->collectData();
$this->export($data);
}
abstract protected function collectData(): array;
abstract protected function export(array $data): void;
}
Concrete implementations specialize behavior without breaking the algorithm structure.
That’s safe inheritance.
Prefer Composition Over Inheritance
Instead of:
class EmailLogger extends FileLogger
Use composition:
final class LoggerFormatter
{
public function format(string $message): string
{
return date('Y-m-d') . ' - ' . $message;
}
}
final class EmailLogger
{
public function __construct(
private LoggerFormatter $formatter
) {}
public function send(string $message): void
{
$formatted = $this->formatter->format($message);
// send email
}
}
Now:
✔ No structural coupling
✔ Reuse without hierarchy
✔ Replaceable components
✔ Safer evolution
Composition isolates change.
Inheritance propagates change.
The Fragility Problem
Inheritance creates:
- Tight coupling
- Implicit dependencies
- Hidden assumptions
- Deep hierarchy complexity
- Hard-to-reason systems
The deeper the hierarchy,
the harder the system becomes to maintain.
Modern architectures prefer shallow hierarchies and composition.
Advanced Insight: Inheritance and Encapsulation Conflict
Inheritance can weaken encapsulation.
Protected properties allow subclasses to manipulate internal state.
That breaks invariants.
That weakens object boundaries.
That introduces subtle bugs.
This is why many modern systems favor:
- Final classes
- Composition
- Interfaces over abstract base classes
When NOT to Use Inheritance
Avoid inheritance when:
- You only want code reuse
- You cannot guarantee behavioral substitution
- The base class wasn’t designed for extension
- You foresee independent evolution
Final Insight
Inheritance is about specialization, not reuse.
If encapsulation protects internal state
and abstraction protects dependency boundaries
Inheritance must protect behavioral contracts.
Used correctly, it models true “is-a” relationships.
Used carelessly, it introduces silent architectural decay.
Inheritance is powerful.
That’s why it must be used carefully.
Thanks for reading!
If you have any questions, complaints or tips, you can leave them here in the comments. I will be happy to answer!
😊😊 See you! 😊😊
Support Me
Youtube - WalterNascimentoBarroso
Github - WalterNascimentoBarroso
Codepen - WalterNascimentoBarroso
Top comments (0)