EMZETT.
Login

Composition over inheritance: Assembling objects flexibly

In short: Instead of sharing behaviour through deep inheritance trees, objects are assembled from small building blocks; this is more flexible, easier to test and avoids rigid hierarchies.

In more detail: Inheritance expresses “is a” (a dog is an animal), composition “has a” or “uses a” (a car has an engine). A well-known rule of thumb in design is: prefer composition over inheritance.

In Depth

The problem with inheritance

  • Rigid coupling: the subclass depends on internals of the superclass; changes at the top break things below (fragile base class problem).
  • Exploding hierarchies: after Animal → Bird → FlyingBird → NonFlyingBird (penguin, ostrich) it gets confusing; combinations (swimming, flying, running) lead to many classes.
  • Violated expectations (Liskov): a square as a subclass of rectangle breaks assumptions.
  • Only one superclass in many languages (Java, C#).

Composition by example

class Flying:
    def move(self): return "flaps"
class Running:
    def move(self): return "runs"
 
class Bird:
    def __init__(self, movement):
        self.movement = movement      # has a behaviour
    def move_around(self):
        return self.movement.move()
 
sparrow = Bird(Flying())
penguin = Bird(Running())

The behaviour is exchangeable (even at runtime) and addressed via interfaces. This is the strategy pattern; decorators, delegation and dependency injection work similarly.

When inheritance fits

When there really is an “is a” relationship that is stable, and the subclass meaningfully fulfils all properties of the superclass (Liskov substitution principle). Interfaces and traits/mixins also offer safe reuse. Rust and Go deliberately have no classical inheritance and rely on composition and traits/interfaces.

Practical rules

  1. Ask: is this really a special case, or do I just want to reuse code? For the latter composition.
  2. Small, focused classes with one task (single responsibility).
  3. Interfaces instead of concrete classes as dependency.
  4. Keep hierarchies flat (at most 2 to 3 levels).
  5. Tests become simpler because building blocks can be replaced individually (mocked).

See also: Polymorphism, SOLID principles, Strategy pattern, Dependency injection, Abstract class or interface