Composition over inheritance
Composition over inheritance in object-oriented programming is the principle that classes should achieve polymorphic behavior and code reuse by their composition rather than inheritance from a base or parent class. This is an often-stated principle of OOP, such as in the influential book Design Patterns.
Basics
An implementation of composition over inheritance typically begins with the creation of various interfaces representing the behaviors that the system must exhibit. Interfaces enable polymorphic behavior. Classes implementing the identified interfaces are built and added to business domain classes as needed. Thus, system behaviors are realized without inheritance.In fact, business domain classes may all be base classes without any inheritance at all. Alternative implementation of system behaviors is accomplished by providing another class that implements the desired behavior interface. A class that contains a reference to an interface can support implementations of the interface - a choice that can be delayed until run time.
Example
Inheritance
An example in C++ follows:class Object
class Visible : public Object
class Solid : public Object
class Movable : public Object
Then, suppose we also have these concrete classes:
- class
Player
- which isSolid
,Movable
andVisible
- class
Cloud
- which isMovable
andVisible
, but notSolid
- class
Building
- which isSolid
andVisible
, but notMovable
- class
Trap
- which isSolid
, but neitherVisible
norMovable
VisibleAndSolid
, VisibleAndMovable
, VisibleAndSolidAndMovable
, etc. for every needed combination, though this leads to a large amount of repetitive code. Keep in mind that C++ solves the diamond problem of multiple inheritance by allowing virtual inheritance.Composition and interfaces
The C++ and 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#.class Object
class VisibilityDelegate
class Invisible : public VisibilityDelegate
class Visible: public VisibilityDelegate
class CollisionDelegate
class Solid : public CollisionDelegate
class NotSolid : public CollisionDelegate
class UpdateDelegate
class Movable : public UpdateDelegate
class NotMovable : public UpdateDelegate
Then, concrete classes would look like:
class Player : public Object
class Smoke : public Object
The following C# example demonstrates the principle of using composition and interfaces to achieve code reuse and polymorphism.
class Program
interface IVisible
class Invisible : IVisible
class Visible : IVisible
interface ICollidable
class Solid : ICollidable
class NotSolid : ICollidable
interface IUpdatable
class Movable : IUpdatable
class NotMovable : IUpdatable
class GameObject : IVisible, IUpdatable, ICollidable
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 is 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.
Some languages, notably Go, 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.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:
- Raku provides a
handles
keyword to facilitate method forwarding. - Java provides Project Lombok which allows delegation to be implemented using a single @Delegate annotation on the field, instead of copying and maintaining the names and types of all the methods from the delegated field.. Java 8 allows for default methods in an interface, similar to C# etc.
- Julia macros can be used to generate forwarding methods. Several implementations exist such as and .
- 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.
- Kotlin includes the delegation pattern in the language syntax.
- Go type embedding avoids the need for forwarding methods.
- D provides an explicit "alias this" declaration within a type can forward into it every method and member of another contained type.
- C# provides default interface methods since 8.0 version which allows to define body to interface member.
- Rust provides traits with default implementations.
Empirical studies