OOP: A clear code comparison of the “is-a” (Inheritance) vs “has-a” (Composition) relationships.

When designing an OOP system, should you favor Composition (“has-a” relationship) or Inheritance (“is-a” relationship)?

You have to know that, in the scenario of modern object-oriented design principles, there is a strong preference for Composition over Inheritance as better design principle.

The key difference:

Inheritance (extends): Creates an “is-a” hierarchy, meaning the subclass is the superclass (e.g., Dog is a Animal). It is tightly coupled.

Composition (Instance Variable): Creates a “has-a” relationship, meaning the enclosing class has the other class as a component (e.g., Car has an Engine). It is loosely coupled and preferred for flexibility.

Why Favor Composition?

  • Flexibility: With Composition (“has-a”), you can change the behavior of a class at runtime by simply swapping out one component object for another. Inheritance (“is-a”) is fixed at compile time.
  • Avoids the “Fragile Base Class” Problem: A change to the base (parent) class in an inheritance hierarchy can unintentionally break the functionality of many derived (child) classes. Composition avoids this tight coupling.
  • Encourages Better Design: Inheritance is best reserved for situations where a clear, verifiable “is-a” relationship exists (e.g., a Dog is-a Mammal). Composition is better for situations where a class simply needs certain functionalities (e.g., a Car has-a Engine).

“Talk is cheap. Show me the code!”

Ok… Let’s see the code:

“is-a”: Inheritance (Tightly Coupled):

// "is-a": Inheritance (Tightly Coupled)

class Engine {
    // The base component (Superclass)
    public String start() {
        return "Engine is running.";
    }
}

class PetrolCar extends Engine {
    /* A PetrolCar IS-A Engine (poor design, but demonstrates 'is-a').
    It inherits the start() method from Engine. 
    */
    public String drive() {
        // Accesses the inherited method directly
        return start() + " The car is driving on petrol.";
    }
}

// Example Usage
public class InheritanceExample {
    public static void main(String[] args) {
        PetrolCar myCar = new PetrolCar();
        System.out.println("Inheritance Example: " + myCar.drive());
    }
}

has-a”: Composition (Loosely Coupled/Flexible)

// "has-a": Composition (Loosely Coupled/Flexible)

class Engine {
    // The component class
    public String start() {
        return "Engine is running.";
    }
}

class Car {
    // The Car HAS-A Engine object (Composition)
    private Engine engine;

    public Car() {
        // The Car creates (or is given) its Engine object
        this.engine = new Engine();
    }

    public String drive() {
        // The Car DELEGATES the task to its component object
        String engineStatus = this.engine.start();
        return engineStatus + " The car is driving using its composed engine.";
    }
    
    // Benefit of Composition: Easy to swap components (e.g., adding a setter)
    public void replaceEngine(Engine newEngine) {
        this.engine = newEngine;
    }
}

// Example Usage
public class CompositionExample {
    public static void main(String[] args) {
        Car myCar = new Car();
        System.out.println("Composition Example: " + myCar.drive());
    }
}

So, should I never use inheritance? Or, when should I use inheritance?

You should use Inheritance when you have a very clear, verifiable “is-a” relationship between two classes. This is the only scenario where inheritance is the better choice, something like: Square is a Shape, Manager is an Employee or Sedan is a Car.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *