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
ClassRoomcan have zero or moreStudentobjects 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
Caris made up of parts likeWheels,Engine, andGearbox. - 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:
- Strong Dependency
These are required for the car to exist.
Example:Engine,Wheelsclass 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, aCarcannot be constructed. - Weak (Optional) Dependency
These are not required for the car to exist but are needed for certain operations.
Example:Petrolis only needed when the car is driven.class Car { void drive(Petrol petrol) { System.out.println("Driving with " + petrol.getType()); } }TheCarobject can exist withoutPetrol, but it requires petrol to perform a specific function (driving).
Hence,Petrolis an optional dependency.
💡 Summary
| Concept | Description | Symbol | Example |
|---|---|---|---|
| Aggregation | Objects have a weak “has-a” relationship; can exist independently | ◊ (Hollow Diamond) | ClassRoom ↔ Students |
| Composition | Objects have a strong “part-of” relationship; cannot exist independently | ◆ (Filled Diamond) | Car ↔ Engine/Wheels |
| Dependency Injection | A class declares required or optional dependencies that are supplied externally | N/A | Car 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.
