Learnitweb

Aggregation vs Composition in Java

In Java and other object-oriented programming languages, understanding how objects relate to each other is essential to writing effective and maintainable code. Two commonly used object-oriented design principles are Aggregation and Composition. These are both types of association that define a “has-a” relationship between classes. However, they differ significantly in how they manage object lifecycles, coupling strength, and the degree of ownership one class has over another.

This tutorial explains both concepts in detail, covers differences, real-life analogies, UML notations, memory behavior, coding examples, and design principles related to Aggregation and Composition in Java.

Understanding Association

Before discussing Aggregation and Composition, it is important to understand the term Association.

Association refers to a general relationship between two or more objects. It implies that objects can be connected or related in some way, allowing one object to interact with another. This can be a broad connection and may include a wide range of relationship types.

For example, a teacher and a student have an association. A teacher teaches many students, and a student can be taught by multiple teachers. This relationship is bidirectional, and both entities exist independently of each other.

Association becomes more specific and structured when we introduce concepts such as Aggregation and Composition.

What is Aggregation?

Aggregation is a form of association that represents a weak “has-a” relationship between two classes. In Aggregation, one class (called the parent or container) holds a reference to another class (called the child or contained object), but the child object can exist independently of the parent.

For example, consider a relationship between a Department and an Employee. A Department contains Employees, but even if the Department is deleted or shut down, the Employees may still exist or be reassigned to another Department. Hence, the lifecycle of the Employee is not tightly bound to the Department. This relationship is considered Aggregation.

From a coding perspective, Aggregation is implemented by defining a member variable of one class type inside another class and passing that object externally, typically via a constructor or setter.

What is Composition?

Composition is a stronger form of association than Aggregation. It represents a strong “has-a” relationship, where the child object cannot exist independently of the parent object. The lifecycle of the child is strictly tied to the lifecycle of the parent. If the parent object is destroyed, the child object is also destroyed automatically.

A classic example of Composition is the relationship between a House and its Rooms. A Room is meaningful only in the context of a House. If the House is destroyed, the Room ceases to exist as well. You would not typically find a standalone Room without a House in this context.

In Java, Composition is implemented by creating the child object inside the constructor of the parent class. The child is not passed from outside but is constructed as part of the parent object’s creation.

Differences Between Aggregation and Composition

While both represent a “has-a” relationship, they differ in several key aspects:

  1. Ownership and Lifecycle In Aggregation, the child object is not owned by the parent object. It can live without the parent and may be shared across multiple parent objects. In Composition, the parent object strictly owns the child object. The child cannot exist outside the context of the parent.
  2. Dependency Aggregation represents a loose dependency. Changes or removal of the parent object do not affect the child. Composition introduces a strong dependency. The child object’s lifecycle is tied to the parent, making it impossible for the child to live independently.
  3. Reusability Aggregation offers better reusability and testability since objects are loosely coupled. The same object instance can be reused across different parent objects. Composition enforces tighter control but limits reusability because the child is embedded within the parent and is usually not shared.
  4. UML Representation In UML diagrams, Aggregation is represented using a hollow diamond at the parent side, while Composition is represented using a filled diamond.
  5. Coupling Aggregation results in loose coupling between classes, making the system more flexible. Composition results in tight coupling, which enhances encapsulation but reduces flexibility.
  6. Garbage Collection Behavior In Aggregation, the child object might not be garbage collected when the parent is destroyed if other references to the child still exist. In Composition, the child is usually eligible for garbage collection as soon as the parent object is destroyed, since no external references to the child are maintained.

Real-Life Analogies

Understanding these concepts through real-world analogies helps reinforce the difference:

  • Aggregation Example: A University and Colleges. A University consists of several Colleges. However, Colleges can exist even if the University shuts down or merges into another institution.
  • Composition Example: A Library and its Bookshelves. If the Library is demolished, the Bookshelves typically are not reused elsewhere and are disposed of along with the Library.

Why Composition is Preferred Over Inheritance

Composition is often considered superior to inheritance because it allows greater flexibility in design. Inheritance creates an “is-a” relationship, which may lead to a rigid hierarchy and tight coupling between classes.

In contrast, Composition allows you to construct complex behaviors by combining objects. You can change behavior at runtime by replacing components, without modifying the class hierarchy.

This aligns well with modern design principles like “favor composition over inheritance”, a common theme in design patterns and architectural best practices.

Tight Coupling vs Loose Coupling

Tight coupling means one class is highly dependent on the inner workings of another class. This creates a fragile system where changes to one class often break the functionality of the dependent class.

Loose coupling minimizes dependencies between classes. It promotes using interfaces, abstraction, and dependency injection to reduce interdependence. Aggregation promotes loose coupling, while Composition leads to tighter coupling.

Frameworks like Spring emphasize loose coupling through techniques like dependency injection, which allow easy substitution of implementations and better unit testing.

Can Aggregation and Composition Be Implemented Without Constructors?

Yes, both can be implemented without using constructors. While constructors are commonly used to establish object relationships, Java allows using setter methods or direct assignment to associate one object with another.

For example, in Aggregation, you can first create the child object independently and then associate it using a setter method.

Employee emp = new Employee(101, "Ajay");
Department dept = new Department();
dept.setEmployee(emp); // Aggregation using setter

In Composition, while the child is usually created in the constructor, you may also initialize it in an initialization block or private method to maintain strict control.

Java Code Examples

Aggregation Example: Department and Employee

class Employee {
    private String name;
    private int id;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getName() { return name; }
    public int getId() { return id; }
}

class Department {
    private String deptName;
    private Employee employee;

    public Department(String deptName, Employee employee) {
        this.deptName = deptName;
        this.employee = employee;
    }

    public void showDepartmentInfo() {
        System.out.println("Department: " + deptName);
        System.out.println("Employee ID: " + employee.getId());
        System.out.println("Employee Name: " + employee.getName());
    }
}

public class AggregationExample {
    public static void main(String[] args) {
        Employee emp1 = new Employee(101, "Ajay Kumar");
        Department dept1 = new Department("IT", emp1);
        dept1.showDepartmentInfo();
    }
}

In this example, the Employee object is created independently and passed to the Department. This means the Employee object can be reused or managed independently of the Department.

Composition Example: House and Room

class Room {
    private String roomType;

    public Room(String roomType) {
        this.roomType = roomType;
    }

    public String getRoomType() {
        return roomType;
    }
}

class House {
    private final Room room;

    public House(String roomType) {
        this.room = new Room(roomType);
    }

    public void showHouseInfo() {
        System.out.println("House has a " + room.getRoomType());
    }
}

public class CompositionExample {
    public static void main(String[] args) {
        House house = new House("Bedroom");
        house.showHouseInfo();
    }
}

In this example, the Room object is created within the constructor of the House. This means the Room object is not accessible outside the House, and cannot be reused. If the House is destroyed, so is the Room.

Summary and Best Practices

When designing classes in Java:

  • Use Aggregation when the child object should live independently of the parent. This allows flexibility, reusability, and better unit testing.
  • Use Composition when the child object must be strictly owned by the parent and should not outlive it. This promotes stronger encapsulation and control.
  • Prefer Composition over Inheritance to promote more modular and testable code.
  • Favor Loose Coupling for long-term maintainability, unless strong control and binding are necessary.