UML Overview: Aggregation, Composition, and Dependency Injection Explained

Understanding UML (Unified Modeling Language) is essential for software architects and developers to visualize the structure and relationships between objects in a system.
In this article, we’ll look at three key UML concepts — Aggregation, Composition, and Dependency Injection — using simple, real-world examples.


🧩 What is UML?

UML (Unified Modeling Language) is a standardized way to model the design of a software system.
It provides diagrams and notations to describe how different parts of a system interact, how objects are related, and how data flows between components.

Developers use UML to model business logic, system design, and object-oriented relationships, making complex architectures easier to understand and maintain.


⚪ UML Aggregation

Aggregation represents a “has-a” relationship where one object contains a reference to another object (or a collection of them), but both can exist independently.

Example:

ClassRoom ↔ Students

  • A ClassRoom can have zero or more Student objects inside it.
  • Even if there are no students, the classroom still exists.
  • So, the existence of a classroom does not depend on the existence of students.

UML Symbol:

  • Represented by a hollow diamond (◊) at the container (whole) side of the relationship.
ClassRoom ◊───▶ Student

In Code (Conceptual Example)

class Student {
    String name;
}

class ClassRoom {
    List<Student> students = new ArrayList<>();
}

Here, even if students is empty, the ClassRoom object is still valid — this is aggregation.


⚫ UML Composition

Composition is a stronger form of aggregation. It represents a whole-part relationship where one object cannot exist without its components.

Example:

Car ↔ Wheel

  • A Car is made up of parts like Wheels, Engine, and Gearbox.
  • Without these parts, a car cannot function or exist meaningfully.
  • If the car is destroyed, its wheels and engine are also destroyed — they do not exist independently.

UML Symbol:

  • Represented by a filled diamond (◆) at the composite (whole) side of the relationship.
Car ◆───▶ Wheel

In Code (Conceptual Example)

class Wheel {
    int size;
}

class Car {
    private Engine engine;
    private Wheel[] wheels;

    Car() {
        engine = new Engine();
        wheels = new Wheel[] { new Wheel(), new Wheel(), new Wheel(), new Wheel() };
    }
}

Here, the Car creates and owns its components. When the Car object is destroyed, its Engine and Wheels cease to exist — this is composition.


🧱 Encapsulation and Interfaces

In large systems, each component should hide its internal implementation and expose only a simple interface for other components to interact with.

Example:

interface ImageManager {
    void uploadImage(File image);
    void deleteImage(String imageId);
    Image getImage(String imageId);
}

Here, the internal logic of how the image is processed, stored, or retrieved is hidden behind the ImageManager interface.
This makes your system modular, testable, and maintainable.


⚙️ Dependency Injection (DI)

Dependency Injection is a design principle that allows a class to declare what it needs (its dependencies) without creating them directly.
This promotes loose coupling between classes and improves testability and scalability.

Example:

Let’s consider a Car again.

A Car depends on objects like Engine, Wheels, and Petrol to operate.

Types of Dependencies:

  1. Strong Dependency
    These are required for the car to exist.
    Example: Engine, Wheels class Car { private Engine engine; private Wheels wheels; // Strong dependencies passed through constructor Car(Engine engine, Wheels wheels) { this.engine = engine; this.wheels = wheels; } } These are mandatory dependencies. Without them, a Car cannot be constructed.
  2. Weak (Optional) Dependency
    These are not required for the car to exist but are needed for certain operations.
    Example: Petrol is only needed when the car is driven. class Car { void drive(Petrol petrol) { System.out.println("Driving with " + petrol.getType()); } } The Car object can exist without Petrol, but it requires petrol to perform a specific function (driving).
    Hence, Petrol is an optional dependency.

💡 Summary

ConceptDescriptionSymbolExample
AggregationObjects have a weak “has-a” relationship; can exist independently◊ (Hollow Diamond)ClassRoom ↔ Students
CompositionObjects have a strong “part-of” relationship; cannot exist independently◆ (Filled Diamond)Car ↔ Engine/Wheels
Dependency InjectionA class declares required or optional dependencies that are supplied externallyN/ACar depends on Engine, Wheels, Petrol

🧠 Final Thoughts

  • Use Aggregation when objects can exist independently.
  • Use Composition when one object’s lifetime depends on another.
  • Use Dependency Injection to make systems loosely coupled and easily testable.
  • Always hide complex logic behind interfaces, exposing only what’s necessary.

These UML principles form the foundation for clean architecture, modular design, and scalable software systems.


Leave a Comment

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

Scroll to Top