esc
Anthology / Yagnipedia / Composition Over Inheritance

Composition Over Inheritance

Favor Has-A Over Is-A, Or Else
Principle · First observed 1994 (Design Patterns, Gang of Four) · Severity: Critical

Composition Over Inheritance is a software design principle stating that objects should achieve polymorphic behavior and code reuse by containing other objects rather than inheriting from a base class. It was formally recommended by the Gang of Four in 1994, immediately ignored by the entire Java ecosystem for approximately fifteen years, and then quietly enforced by Go, which settled the debate by making inheritance impossible.

“You wouldn’t build a car by extending AbstractMotorizedLandVehicle. You’d bolt an engine to a chassis. This is not a difficult concept, and yet I have seen hierarchies deeper than the Mariana Trench, and twice as dark.”
The Lizard, on the nature of things

The Principle

The idea is simple enough to fit on a napkin, which is where most good architecture ends up anyway: rather than defining what an object is through a chain of ancestors, define what it does by assembling capabilities from smaller, self-contained pieces.

A Car does not need to be an AbstractMotorizedLandVehicle which is an AbstractMotorizedVehicle which is an AbstractVehicle which is a Thing. A Car has an engine, has wheels, and has a stereo that plays music nobody in the back seat wants to hear. The distinction matters, because when you need to change the engine, you change the engine — you do not restructure the philosophical lineage of all vehicles.

The Java Years

Between roughly 1996 and 2011, the Java ecosystem conducted the largest uncontrolled experiment in deep inheritance hierarchies that civilization has ever produced. The results were not encouraging.

Enterprise Java gave us AbstractSingletonProxyFactoryBean, which is a real class that really existed in the Spring Framework, and which serves as the platonic ideal of inheritance gone wrong. It did not compose behavior from small pieces. It was behavior, in the same way that a black hole is gravity — technically correct, but you wouldn’t want to work near one.

“I once traced an inheritance chain fourteen classes deep to find where a method was actually implemented. FOURTEEN. I started on Monday. I found it on Wednesday. The method returned true.”
The Caffeinated Squirrel, vibrating at a frequency visible to bats

The Go Reformation

Go resolved the composition-versus-inheritance debate with the diplomatic elegance of a language designed by people who had seen things: it simply removed inheritance from the language entirely. There are no classes. There is no extends. There is embedding, there are interfaces, and there is a quiet expectation that you will compose your types from small pieces like an adult.

This was controversial for approximately six months, after which Go developers discovered that they had not once needed a fourteen-class hierarchy to return true.

“I remember inheritance. I remember the trees of types, branching endlessly, each leaf a specialization of a specialization. They were beautiful in their way. Like family trees of European monarchies — elaborate, fragile, and prone to catastrophic problems when any two branches needed to merge.”
— A Passing AI, staring at a Go interface with exactly two methods

Lego Architecture

The most instructive metaphor for composition comes from the world of interlocking plastic bricks, as observed in The Databases We Didn’t Build: “You don’t build Lego bricks. You build WITH Lego bricks.”

This is the essential insight. SQLite is a brick. JetStream is a brick. DuckDB is a brick. You do not extend AbstractPersistenceLayer and override seventeen methods. You compose a storage layer from pieces that each do one thing well, connected by interfaces thin enough to read in a single breath.

Go enforces this not through guidelines or code reviews or sternly worded wiki articles, but through the simple mechanism of not giving you any other option. This is the most effective form of architectural governance: making the wrong thing impossible rather than merely discouraged.

The Diamond Problem

Inheritance’s most famous failure mode is the Diamond Problem, in which a class inherits from two classes that share a common ancestor, and the runtime must decide which grandparent’s method to call. Languages have invented increasingly baroque solutions: C++ uses virtual inheritance, Python uses the Method Resolution Order algorithm, and Go uses not having the problem because it does not have inheritance.

“The Diamond Problem is what happens when you let types have family reunions. Composition doesn’t have family reunions. Composition has a clean dependency graph and goes home at five.”
— The Caffeinated Squirrel, who has never gone home at five

Signs You Need Composition

The following symptoms indicate an inheritance hierarchy that should have been composition:

The Pendulum

As with all principles, composition over inheritance has its zealots. Some developers, upon learning that inheritance is problematic, conclude that all shared behavior must be achieved through composition, including cases where a simple is-a relationship would have been perfectly fine. A Square really is a Rectangle. You do not need to compose it from a WidthHaver and a HeightHaver.

This overcorrection is related to Premature Abstraction and is documented in the literature as Compulsive Composition Disorder, though The Lizard prefers to call it “using a strategy pattern to tie your shoes.”

“The goal was never to eliminate inheritance. The goal was to stop building cathedrals when you needed a shed. Some people heard this and decided to also stop building sheds.”
— The Lizard, sighing in a way that suggested millennia of patience

See Also