Skip to content

Chapter 7 of 8

Composition, Inheritance, and Code Reuse

Both composition and inheritance are ways to build classes out of other classes, but they capture fundamentally different relationships. Inheritance models an is-a relationship, as in Dog is an Animal, while composition models a has-a relationship, as in Car has an Engine. The widely cited principle to favor composition over inheritance reflects the fact that composition provides greater flexibility and avoids many of the pitfalls of inheritance hierarchies.

Composition is generally preferred for several reasons. It produces loose coupling, allowing components to be swapped at runtime. It sidesteps the fragile base class problem, where seemingly safe changes to a parent class unintentionally break subclasses. It offers greater flexibility by allowing a class to combine behaviors from multiple sources, and it avoids the deep, hard-to-understand hierarchies that complex inheritance trees tend to create. Inheritance remains the right choice only when a true is-a relationship genuinely exists and the behavioral contract is well understood.

Several modern mechanisms exist for code reuse that sidestep the limitations of single inheritance. Mixins are classes that provide reusable methods to other classes without serving as a standalone base, exemplified in Python by a JsonMixin that adds to_json behavior to any class that mixes it in. Traits are similar reusable sets of methods used in languages like Scala, PHP, and Rust, with explicit mechanisms for resolving conflicts when multiple traits provide the same method. Both approaches promote code reuse without forcing deep inheritance hierarchies.

Multiple inheritance, the ability for a class to inherit from more than one parent class, is supported in languages like C++ and Python but disallowed in Java and C#. Its main risk is the diamond problem, which arises when a class inherits from two classes that share a common ancestor, creating ambiguity about which version of an inherited method should be used. Languages solve this problem in different ways: Python uses C3 linearization through its method resolution order, C++ offers virtual inheritance, and Java sidesteps the issue entirely by disallowing multiple class inheritance and relying on interfaces instead. The fragile base class problem, where changes to a base class unexpectedly break subclasses, is another reason to favor composition, mark classes as final when not designed for extension, and document which methods are safe to override.

All chapters
  1. 1The Four Pillars of OOP
  2. 2The SOLID Design Principles
  3. 3Classes, Objects, and Language Mechanics
  4. 4Object Relationships
  5. 5Polymorphism and Method Dispatch
  6. 6Abstraction Through Interfaces and Abstract Classes
  7. 7Composition, Inheritance, and Code Reuse
  8. 8General Software Design Principles

Drill it

Reading is not remembering. These come from the Oop Principles deck:

Q

What is encapsulation in OOP?

Encapsulation is the bundling of data and methods that operate on that data into a single unit (class), while restricting direct access to some components.Key b...

Q

What is inheritance in OOP?

Inheritance allows a child class (subclass) to acquire the properties and behaviors of a parent class (superclass).Example in Java:class Dog extends Animal { }B...

Q

What is polymorphism in OOP?

Polymorphism means "many forms" — the ability of an object to take on different behaviors depending on its type.Two main types:Compile-time (static): method ove...

Q

What is abstraction in OOP?

Abstraction is the process of hiding complex implementation details and exposing only the essential features of an object.Achieved through:Abstract classes — pa...