Skip to main content

Composition Over Inheritance

Favor object composition over inheritance to achieve flexibility, reduce fragility, and improve code reuse.

TL;DR

Inheritance creates rigid "is-a" relationships and fragile base classes. Composition creates flexible "has-a" relationships where objects delegate to components. Inheritance is appropriate for true hierarchies (Dog is-a Animal). Composition is better for feature combinations (Car has-a Engine, has-a Transmission). Composition enables runtime flexibility, easier testing, and simpler code evolution.

Learning Objectives

You will be able to:

  • Distinguish between "is-a" and "has-a" relationships
  • Identify inheritance hierarchies that should be composition
  • Refactor inheritance-based code toward composition
  • Use composition patterns (decorator, strategy, delegation)
  • Recognize when inheritance is genuinely appropriate

Motivating Scenario

A codebase has Vehicle > Car hierarchy. Later, Car needs TaxiDispatchMixin for ride-sharing. Then TrafficReporting. Then ParkingMeterIntegration. The Car class now inherits from four different mixins, creating a fragile diamond problem. Adding a new feature requires modifying the inheritance chain or creating new subclasses.

With composition: Car contains a DispatchService, a TrafficService, a ParkingService. Adding a feature means adding a component, not reorganizing the hierarchy. Car's implementation stays simple.

Core Concepts

Inheritance: "Is-A" Relationship

Inheritance models hierarchical relationships where a subclass is a specialized version of its parent.

Dog is-a Animal
Square is-a Shape
ElectricCar is-a Car

Composition: "Has-A" Relationship

Composition models capability relationships where an object contains components that provide behavior.

Car has-a Engine
Car has-a Transmission
Employee has-a Address
Inheritance vs. Composition

The Fragile Base Class Problem

Inheritance creates tight coupling between parent and child. Changes to the base class can break subclasses. Adding features often requires modifying the inheritance hierarchy, affecting all subclasses.

Practical Example

# ❌ INHERITANCE - Brittle and inflexible
class Animal:
def speak(self):
pass

class Dog(Animal):
def speak(self):
return "Woof"

class Cat(Animal):
def speak(self):
return "Meow"

class RobotDog(Dog): # Inherits from Dog but isn't really a Dog
def speak(self):
return "Beep Boop"

def charge(self): # New capability mixed in
return "Charging..."

# ✅ COMPOSITION - Flexible and composable
class Speaker:
def speak(self):
raise NotImplementedError

class DogSpeaker(Speaker):
def speak(self):
return "Woof"

class RobotSpeaker(Speaker):
def speak(self):
return "Beep Boop"

class Battery:
def charge(self):
return "Charging..."

class Dog:
def __init__(self, speaker, battery=None):
self.speaker = speaker
self.battery = battery

def speak(self):
return self.speaker.speak()

def charge(self):
if self.battery:
return self.battery.charge()
return "Cannot charge"

# Usage - flexible combinations
real_dog = Dog(DogSpeaker())
robot_dog = Dog(RobotSpeaker(), Battery())
robot_dog.speak() # "Beep Boop"
robot_dog.charge() # "Charging..."

When to Use / When Not to Use

✓ Use Composition When

  • Objects combine multiple features or capabilities
  • The relationship is "has-a" not "is-a"
  • You need runtime flexibility in behavior
  • Inheritance hierarchies would be deep or wide
  • Multiple objects share similar behavior but aren't related hierarchically

✓ Use Inheritance When

  • True hierarchical "is-a" relationships exist
  • Sharing implementation across the hierarchy is important
  • The inheritance is shallow and stable
  • Polymorphism relies on inheritance (interfaces in composition are often better)
  • The hierarchy reflects domain concepts, not implementation

Patterns and Pitfalls

Pitfall: Inheritance for Code Reuse

Using inheritance to share code creates coupling. Use composition instead:

# ❌ Inheritance for reuse
class Logger:
def log(self, msg):
print(msg)

class Service(Logger): # Inherits just for log method
pass

# ✅ Composition for reuse
class Service:
def __init__(self, logger):
self.logger = logger

def do_something(self):
self.logger.log("Doing something")

Pattern: Strategy Pattern

Use composition to switch behavior at runtime:

class PaymentProcessor:
def __init__(self, strategy):
self.strategy = strategy

def process(self, amount):
return self.strategy.process(amount)

processor = PaymentProcessor(CreditCardStrategy())
processor.process(100)

Pattern: Decorator Pattern

Wrap objects to add behavior without inheritance:

class BufferedLogger:
def __init__(self, logger):
self.logger = logger
self.buffer = []

def log(self, msg):
self.buffer.append(msg)
if len(self.buffer) >= 10:
self.flush()

def flush(self):
for msg in self.buffer:
self.logger.log(msg)
self.buffer = []

Design Review Checklist

  • Is the 'is-a' relationship a true type hierarchy?
  • Would 'has-a' better describe the relationship?
  • Is inheritance being used just to reuse code?
  • Could this behavior be added to an object dynamically?
  • Would composition allow easier testing and mocking?
  • Is the inheritance hierarchy shallow and stable?
  • Are there multiple inheritance paths or deep hierarchies?
  • Can child classes be substituted for parent class instances?

Self-Check

  1. Look at your inheritance hierarchies. Are they modeling domain concepts or implementation convenience?

  2. Could you achieve the same behavior with composition? What would change?

  3. How often do you add new subclasses versus modify the base class?

info

One Takeaway: Inheritance creates rigid "is-a" hierarchies that become fragile as requirements change. Composition creates flexible "has-a" relationships where objects delegate to components. Most features can be expressed through composition, gaining flexibility and simplicity.

Next Steps

References

  1. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional.
  2. Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall.
  3. McConnell, S. (2004). Code Complete: A Practical Handbook of Software Construction (2nd ed.). Microsoft Press.
  4. Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd ed.). Addison-Wesley Professional.