Introduction
When learning object‑oriented design, it’s tempting to use inheritance for almost everything.
You create base classes, extend them, and end up with deep hierarchies that are hard to change.
The Composing Objects Principle is a reminder that there is usually a better way:
Instead of inheriting behavior, compose behavior by giving objects collaborators. This leads to designs that are easier to extend, test, and reason about.
This article explains the principle in simple language, shows examples of inheritance vs composition, and helps you decide which approach to use in your low-level designs.
Core idea: “Has‑a” vs “Is‑a.”
There are two main relationships you use in OOP:
“Is‑a” (inheritance):
A
Dogis anAnimal.A
Circleis aShape.
“Has‑a” (composition):
A
Carhas anEngine.A
Userhas aProfile.An
Orderhas aDiscountStrategy.
The Composing Objects Principle says:
When you want to reuse behavior or build more complex objects, prefer “has‑a” (composition) over “is‑a” (inheritance) unless the subtype relationship is very clear and stable.In practice:
Instead of “Car extends Engine,” just to reuse
start(),Design “Car has an Engine” and call
engine.start().
Why inheritance alone can hurt your design
Overusing inheritance creates several problems:
Tight coupling
Subclasses depend heavily on internal details of their superclasses.
Changing the base class can unintentionally break many children.
Rigid hierarchies
Once you set up a class tree, it’s hard to rearrange when requirements evolve.
You might force unrelated concepts into the same hierarchy just to reuse a few methods.
Wrong domain modeling
You model relationships that don’t make sense in the real world.
For example, “Car is an Engine” is wrong; a car has an engine.
Composition avoids many of these issues by letting you plug small, focused objects together instead of sharing everything through inheritance.
Example 1: Car and Engine
Misusing inheritance
Here, Car is an Engine. That makes little sense conceptually.
Also, Car now inherits all of Engine’s internals, even if it only needs start().
Using composition instead
Now:
The domain model matches reality: a car has an engine.
Enginecan be replaced (e.g., byElectricEngine) without changingCar’s type or inheritance.CarandEngineare less tightly coupled.
This is a simple but powerful example of the composing objects principle.
Example 2: Pluggable behavior with composition (Strategy‑style)
Suppose you want different discount behaviors:
No discount
Percentage discount
Fixed discount
Instead of making subclasses, you can compose order behavior from a discount object.
Define the discount abstraction
Implement multiple strategies
Compose the behavior in Order
Now:
Orderhas aDiscountStrategy.You can plug in different discounts at runtime or in tests.
You did not need a separate
OrderWithPercentageDiscountsubclass,OrderWithNoDiscountsubclass, etc.
This pattern—swapping behavior by composing objects—is a direct application of the composing objects principle.
Patterns that use composition
Several classic design patterns are really just structured ways of composing objects:
Strategy
Let a class reference an interchangeable “strategy” object to vary its behavior.
Decorator
Wrap an existing object with another object that adds behavior (like logging, caching, validation) without changing the original class.
Composite
Represent tree structures where an object is composed of many smaller objects that share the same interface (e.g., a folder containing files and subfolders).
In all of these, complex behavior is built by assembling smaller parts, not by inheriting everything from a huge base class.
When composition is better, and when inheritance is okay
The principle does not ban inheritance. It helps you choose wisely.
Composition is usually better when:
You want to mix and match independent behaviors (discount + logging + permissions).
You expect behaviors to change or grow over time.
The relationship is clearly “X has Y” (car–engine, user–profile, order–strategy).
You want lower coupling and easier swapping of implementations.
Inheritance is acceptable when:
There is a clear, stable “is‑a” relationship in the domain (e.g.,
Circleis aShape,AdminUseris aUser).The base type defines a stable contract, and your main goal is polymorphism (using many subtypes through a common interface).
Subclasses do not need to know about the fragile internals of the base class.
Even then, keep hierarchies shallow and avoid inheriting from classes just to reuse a couple of convenience methods.
How does this tie into other principles?
The composing objects principle supports and is supported by other design ideas:
Coupling and Cohesion
Composition helps you build classes that are small and cohesive (each with a focused role) and connect them with looser coupling.
SRP (Single Responsibility) and Separation of Concerns
Each composed object can handle one concern, while bigger objects coordinate them.
DIP (Dependency Inversion)
You usually compose via interfaces or abstract classes, so high‑level code depends on abstractions, not concrete details.
KISS and YAGNI
Instead of designing huge inheritance trees for hypothetical futures, you start with simple composed objects and only add more when real needs appear.
Think of it as: keep behavior in small, focused parts and wire them together, instead of centralizing everything in a big family tree of subclasses.
Summary
The Composing Objects Principle says that, for code reuse and flexibility, you should favor composition over inheritance:
Prefer “has‑a” relationships (one object using another as a member) over “is‑a” hierarchies, unless the subtype relationship is clearly correct and stable.
Build complex behavior by plugging together small, focused objects (strategies, decorators, collaborators) instead of inheriting big blocks of behavior.
Use inheritance mainly for genuine polymorphism, not just to reuse a few methods.
Following this principle helps you keep coupling low, cohesion high, and your Low-Level Designs easier to extend and reason about.