Why should we prefer Composition over Inheritance?

In object-oriented programming, both composition and inheritance are ways to reuse code and create relationships between classes. However, there are several reasons why composition is often preferred over inheritance:

  1. Encapsulation: Inheritance can lead to tight coupling between classes, where changes in the superclass can have unexpected consequences on the subclass. Composition, on the other hand, promotes loose coupling and encapsulation, where the internal implementation of a class is hidden from the outside world.
  2. Flexibility: Inheritance is a static relationship that is defined at compile time and cannot be changed at runtime. Composition, on the other hand, is a dynamic relationship that can be changed at runtime, allowing for greater flexibility and adaptability in the system.
  3. Code reuse: Inheritance is often used to reuse code from a superclass, but it can also lead to code duplication and brittle code if the inheritance hierarchy becomes too complex. Composition, on the other hand, promotes code reuse through the use of interfaces and dependency injection, which can lead to more modular and reusable code.
  4. Single Responsibility Principle: Inheritance can lead to classes that have multiple responsibilities, violating the Single Responsibility Principle. Composition, on the other hand, promotes separation of concerns and can lead to more cohesive and maintainable classes.

Here’s an example that illustrates the benefits of composition over inheritance:

// Inheritance

public class Car {
  protected Engine engine;
  
  public Car() {
    engine = new Engine();
  }
  
  public void start() {
    engine.start();
  }
  
  public void stop() {
    engine.stop();
  }
}

public class SportsCar extends Car {
  public void accelerate() {
    engine.increaseThrottle();
  }
  
  public void brake() {
    engine.decreaseThrottle();
  }
}

// Composition

public class Car {
  private Engine engine;
  
  public Car(Engine engine) {
    this.engine = engine;
  }
  
  public void start() {
    engine.start();
  }
  
  public void stop() {
    engine.stop();
  }
}

public class SportsCar {
  private Car car;
  
  public SportsCar(Car car) {
    this.car = car;
  }
  
  public void accelerate() {
    car.getEngine().increaseThrottle();
  }
  
  public void brake() {
    car.getEngine().decreaseThrottle();
  }
}
Java

In the inheritance example, the SportsCar class inherits from the Car class and has access to the Engine object through the protected engine field. This can lead to tight coupling between the SportsCar and Engine classes, and can make it difficult to change the implementation of the Engine class without affecting the SportsCar class.

In the composition example, the SportsCar class has a reference to a Car object, which in turn has a reference to an Engine object. This promotes loose coupling and encapsulation, and allows for greater flexibility and adaptability in the system. Additionally, the SportsCar class only exposes the methods that it needs, promoting separation of concerns and maintaining the Single Responsibility Principle.