Composition

About

Composition is an object-oriented design principle where one object contains another object instead of inheriting from it. It is based on the "has-a" relationship rather than an "is-a" relationship (which is used in inheritance).

Key Idea: Instead of extending a class (inheritance), we create an instance of another class as a field inside our class and use its functionality.

Types of Composition

There are two main types of composition:

1. Strong Composition (Composition) – "Lifelong Dependency"

  • The contained object CANNOT exist without the containing object.

  • Strong ownership: If the main object is destroyed, the contained object is also destroyed.

  • Represented by a filled diamond in UML.

Example: A Car has an Engine (An Engine cannot exist without a Car)

class Engine {
    void start() { System.out.println("Engine starts"); }
}

class Car {
    private final Engine engine = new Engine(); // Engine belongs to Car
    
    void startCar() {
        engine.start();
    }
}

2. Weak Composition (Aggregation) – "Independent Relationship"

  • The contained object CAN exist independently.

  • Weak ownership: If the main object is destroyed, the contained object can still exist.

  • Represented by an empty diamond in UML.

Example: A University has Students, but students can exist without a university.

class Student {
    String name;
    Student(String name) { this.name = name; }
}

class University {
    private List<Student> students = new ArrayList<>();
    
    void addStudent(Student student) {
        students.add(student);
    }
}

Better Reusability: Students can exist outside of a university.

Composition vs. Inheritance

Comparison

Feature

Composition (Preferred)

Inheritance (Use with Caution)

Relationship

"Has-a" (Car has-a Engine)

"Is-a" (Car is-a Vehicle)

Coupling

Loosely Coupled

Tightly Coupled

Code Reusability

High

Moderate

Flexibility

More Flexible (can change components easily)

Less Flexible (inherited behavior is fixed)

Encapsulation

Strong (hides implementation details)

Weak (exposes superclass methods)

Code Maintainability

Easier

Harder (deep inheritance chains lead to complexity)

Multiple Behaviour

Can change behaviour at runtime

Fixed at compile time

Example

A Car contains an Engine

A Car extends Vehicle

When to Use Composition?

  • When we need code reuse without tight coupling.

  • When we need flexibility in changing object behaviour at runtime.

  • When inheritance doesn’t make sense (e.g., a Car is not an Engine, but it has an Engine).

Why Composition is Preferred?

When to Use Inheritance?

  • When objects have a clear hierarchical relationship (is-a).

  • When you need default behaviour sharing across multiple classes.

Code Example: Composition vs. Inheritance

  • Bad Example (Using Inheritance Incorrectly)

class Engine {
    void start() { System.out.println("Engine starts"); }
}

// Incorrect: Car is not an Engine!
class Car extends Engine { }

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();  // Illogical: Car starts like an Engine?
    }
}

Problem: The Car class is not really an Engine, but it extends it, which breaks the "is-a" principle.

  • Good Example (Using Composition Correctly)

class Engine {
    void start() { System.out.println("Engine starts"); }
}

// Correct: Car has an Engine
class Car {
    private Engine engine; // Composition

    Car() { this.engine = new Engine(); } // Injecting dependency

    void startCar() { 
        System.out.println("Car is starting...");
        engine.start(); // Delegating functionality
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.startCar();  // Outputs: Car is starting... Engine starts
    }
}

Better Design: Car contains an Engine but does not inherit from it. This keeps the code flexible and maintainable.

Last updated