Fundamentals 13 min read

When Inheritance Breaks Your Code: Why Composition Often Wins

This article critiques the over‑idealization of object‑oriented programming, explaining how inheritance can create tight coupling and hidden bugs, why encapsulation may leak state, the limits of polymorphism, and how modern stateless service architectures favor composition and functional approaches.

macrozheng
macrozheng
macrozheng
When Inheritance Breaks Your Code: Why Composition Often Wins

“I am a relic of the old era; the new era has no ship to carry me.” If object‑oriented programming (OOP) were a person, it might feel the same today.

The author has used OOP for over a decade, relying on it for architecture, analysis, and coding, but now warns against blind worship of OOP.

Three classic OOP features: inheritance, encapsulation, polymorphism.

These are actually features of OOP languages, not of the OOP philosophy itself.

1. Inheritance can bring endless pain

Inheritance enables code reuse by allowing a subclass to inherit non‑private members of a superclass, but it also creates tight coupling between parent and child classes.

When business models do not fit a strict hierarchy, inheritance can cause problems, such as ambiguous method origins or recursive dependencies.

Example from Wikipedia:

<code>class Super {
  private int counter = 0;
  void inc1() { counter++; }
  void inc2() { counter++; }
}

class Sub extends Super {
  @Override
  void inc2() { inc1(); }
}</code>

If

inc1

is later changed to call

inc2

, an infinite recursion (stack overflow) occurs.

One day as a father, forever an ancestor.

Thus, many guides recommend preferring object composition over inheritance.

Alibaba Java Development Manual illustrates the difference between composition and inheritance (image omitted).

2. Encapsulation can leak like a broken seal

Encapsulation means bundling data and methods inside an object, but in practice references to internal objects can be exposed and mutated, breaking the intended protection.

Example:

<code>A a = new A();
B b = a.getB();
// b is a reference; its fields can be changed directly
b.setS("World");</code>

Such leaks make debugging difficult and violate the spirit of encapsulation.

3. Polymorphism is valuable but not unique to OOP

Polymorphism allows code to handle changing requirements, yet procedural languages (C) and functional languages can achieve similar behavior through function pointers or higher‑order functions.

4. Shifting from stateful to stateless services challenges OOP

Modern distributed and micro‑service architectures favor stateless services for scalability and parallelism. Stateful objects introduce hidden dependencies that hinder determinism and parallel execution.

When services become purely computational, the mutable state inside objects becomes a liability, leading developers to prefer functional or composition‑based designs.

Conclusion

OOP is a tool created to simplify complex domains, but it is not a silver bullet. Its drawbacks stem from human misuse rather than the paradigm itself. Developers should flexibly combine OOP with other paradigms, embrace change, and remember the original purpose of writing code.

software designOOPEncapsulationpolymorphismStatelessinheritancecomposition
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.