Composition over inheritance


In object-oriented programming, composition over inheritance is a common design pattern that tries to achieve code reuse without requiring inheritance. Instead of having two child classes inherit functionality from a common parent, composition simulates inheritance by having the children include a copy of a "pseudo-parent" class as a field, which implements the functionality common to the two classes. Then, methods that would have been called on the child are instead called on the pseudo-parent, a technique called method forwarding.
Composition is generally used in languages where inheritance is unavailable or has an implementation that is considered inflexible, inconvenient, or inadequate. However, many problems that are easily solved with inheritance are difficult to solve using only composition; as a result, inheritance and object composition typically work hand-in-hand, as discussed in the book Design Patterns.

Example

Inheritance

An example in C++ follows:

import std;
using std::unique_ptr;
using std::vector;
class GameObject ;
class Visible : public GameObject ;
class Solid : public GameObject ;
class Movable : public GameObject ;

Then, suppose we also have these concrete classes:
  • class - which is, and
  • class - which is and, but not
  • class - which is and, but not
  • class - which is, but neither nor
Note that multiple inheritance is dangerous if not implemented carefully because it can lead to the diamond problem. One solution to this is to create classes such as,,, etc. for every needed combination; however, this leads to a large amount of repetitive code. C++ uses virtual inheritance to solve the diamond problem of multiple inheritance.

Composition and interfaces

The C++ examples in this section demonstrate the principle of using composition and interfaces to achieve code reuse and polymorphism. Due to the C++ language not having a dedicated keyword to declare interfaces, the following C++ example uses inheritance from a pure abstract base class. For most purposes, this is functionally equivalent to the interfaces provided in other languages, such as Java and C#.
Introduce an abstract class named, with the subclasses and, which provides a means of drawing an object:

class VisibilityDelegate ;
class NotVisible : public VisibilityDelegate ;
class Visible : public VisibilityDelegate ;

Introduce an abstract class named, with the subclasses and, which provides a means of moving an object:

class UpdateDelegate ;
class NotMovable : public UpdateDelegate ;
class Movable : public UpdateDelegate ;

Introduce an abstract class named, with the subclasses and, which provides a means of colliding with an object:

import std;
using std::vector;
class CollisionDelegate ;
class NotSolid : public CollisionDelegate ;
class Solid : public CollisionDelegate ;

Finally, introduce a class named with members to control its visibility, movability, and solidity. This class has methods which delegate to its members, e.g. simply calls a method on the :

import std;
using std::unique_ptr;
using std::vector;
class GameObject ;

Then, concrete classes would look like:

class Player : public GameObject ;
class Smoke : public GameObject ;

Benefits

To favor composition over inheritance is a design principle that gives the design higher flexibility. It is more natural to build business-domain classes out of various components than trying to find commonality between them and creating a family tree. For example, an accelerator pedal and a steering wheel share very few common traits, yet both are vital components in a car. What they can do and how they can be used to benefit the car are easily defined. Composition also provides a more stable business domain in the long term as it is less prone to the quirks of the family members. In other words, it is better to compose what an object can do than extend what it is.
Initial design is simplified by identifying system object behaviors in separate interfaces instead of creating a hierarchical relationship to distribute behaviors among business-domain classes via inheritance. This approach more easily accommodates future requirements changes that would otherwise require a complete restructuring of business-domain classes in the inheritance model. Additionally, it avoids problems often associated with relatively minor changes to an inheritance-based model that includes several generations of classes.
Composition relation is more flexible as it may be changed on runtime, while sub-typing relations are static and need recompilation in many languages.
Some languages, notably Go and Rust, use type composition exclusively.

Drawbacks

One common drawback of using composition instead of inheritance is that methods being provided by individual components may have to be implemented in the derived type, even if they are only forwarding methods. In contrast, inheritance does not require all of the base class's methods to be re-implemented within the derived class. Rather, the derived class only needs to implement the methods having different behavior than the base class methods. This can require significantly less programming effort if the base class contains many methods providing default behavior and only a few of them need to be overridden within the derived class.
Thus, the pattern of "composition over inheritance" should not be interpreted as a literal mantra or law, but rather a selectively applicable suggestion to be used where appropriate.
For example, in the C# code below, the variables and methods of the base class are inherited by the and derived subclasses. Only the method needs to be implemented by each derived subclass. The other methods are implemented by the base class itself, and are shared by all of its derived subclasses; they do not need to be re-implemented or even mentioned in the subclass definitions.

// Base class
public abstract class Employee
// Derived subclass
public class HourlyEmployee : Employee
// Derived subclass
public class SalariedEmployee : Employee

Avoiding drawbacks

This drawback can be avoided by using traits, mixins, embedding, or protocol extensions.
Some languages provide specific means to mitigate this:
  • C# provides default interface methods since version 8.0 which allows to define body to interface member.
  • C++ allows virtual methods to have default implementations.
  • D provides an explicit "alias this" declaration within a type can forward into it every method and member of another contained type.
  • Dart provides mixins with default implementations that can be shared.
  • Go type embedding avoids the need for forwarding methods.
  • Java provides default interface methods since version 8. Project Lombok supports delegation using the annotation on the field, instead of copying and maintaining the names and types of all the methods from the delegated field.
  • Julia macros can be used to generate forwarding methods. Several implementations exist such as Lazy.jl and TypedDelegation.jl.
  • Kotlin includes the delegation pattern in the language syntax.
  • PHP supports traits, since PHP 5.4.
  • Raku provides a trait to facilitate method forwarding.
  • Rust provides traits with default implementations.
  • Scala provides an "export" clause to define aliases for selected members of an object.
  • Swift extensions can be used to define a default implementation of a protocol on the protocol itself, rather than within an individual type's implementation.

Empirical studies

A 2013 study of 93 open source Java programs found that: