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 Dog is an Animal.

    • A Circle is a Shape.

  • “Has‑a” (composition):

    • A Car has an Engine.

    • A User has a Profile.

    • An Order has a DiscountStrategy.

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:

  1. Tight coupling

    • Subclasses depend heavily on internal details of their superclasses.

    • Changing the base class can unintentionally break many children.

  2. 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.

  3. 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.

  • Engine can be replaced (e.g., by ElectricEngine) without changing Car’s type or inheritance.

  • Car and Engine are 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:

  • Order has a DiscountStrategy.

  • You can plug in different discounts at runtime or in tests.

  • You did not need a separate OrderWithPercentageDiscount subclass, OrderWithNoDiscount subclass, 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., Circle is a Shape, AdminUser is a User).

  • 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.