Understanding Relationships in OOP: Aggregation vs Composition
Relationships between classes are very important in the object-oriented programming paradigm. Classes and OOP are built to model real-world entities, and the relationships between classes are introduced to model the connections between real-world entities.
Classes without any relations between them are impractical in OOP, because usually in the real world, almost every entity is somehow connected to another entity.
Common Relationship Types
- One to many — 1 entity is connected with more than 1 instance of another entity.
Ex: Company(1) has employees(N) - Many to many — more than 1 instance of an entity are connected with more than 1 instance of another entity.
Ex: Many courses(N) can be taught by many Lecturers(M) - Many to one — more than 1 instance of an entity are connected with 1 instance of another entity.
Ex: Multiple Lecturers(N) can be assigned to work in 1 department(1) - One to one — 1 entity is connected with only 1 entity.
Ex: 1 Person(1) has only 1 Mother(1)
Forms of Relationships
There are two main forms of relationships in OOP:
- IS-A (Inheritance)
- HAS-A (Association)
IS-A Relationship (Inheritance)
This is exactly Inheritance in OOP. That is, parent–child communication. Examples:
- Car IS A Vehicle
- Cat IS AN Animal
- Manager IS AN Employee
All these examples denote that the subtype is in the form of the supertype, creating an is-a relationship.
HAS-A Relationship (Association)
This means the interconnection between two entities/objects in the real world, simply called Association. Examples:
- Hotel HAS Rooms
- School HAS Departments
- Employee HAS Address
This represents the ownership of an entity in another. One entity belongs to another.
Types of Association in Java
There are two forms of Association that are possible in Java:
- Aggregation — loose coupling, weak
- Composition — tight coupling, strong
Aggregation
In aggregation, entities are loosely coupled together. Each entity can survive on its own, independently. There is only a dependency on the other. If the container is destroyed, the component should be able to survive.
UML diagram for aggregation (Add UML diagram here if desired)
Example:
Imagine we have an Employee
class having id
, name
, and address
. Here, address
has street
and city
. Basically, we have two classes: Employee
and Address
. According to the aggregation definition, the Address
should be independent, and the Employee
is composed of an address. Address
should be able to survive on its own.
Look at the below snippet. Employee
is accepting an address object via the constructor, which means we need an address to create an Employee
. Employee HAS an Address! 😎
public class AggregateEmployee {
private final int id;
private final String name;
private final Address address;
public AggregateEmployee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
@Override
public String toString() {
return "AggregateEmployee{" +
"id=" + id +
", name='" + name + '\'' +
", address=" + address +
'}';
}
}
Client code:
public class AggregationTest {
public static void main(String[] args) {
Address address = new Address("street 1", "city 1");
AggregateEmployee e = new AggregateEmployee(1, "Tim", address);
System.out.println(e);
}
}
The
address
object can live in the code without any help ofEmployee
! The outer world can createAddress
objects without any interference. It’s totally independent. So, we have implemented Aggregation using Java!
Composition
In contrast to aggregation, in composition, entities are tightly coupled together. The dependent entity cannot survive on its own. If the container is destroyed, the component is also destroyed and no longer exists.
UML diagram for composition (Add UML diagram here if desired)
Let’s take the same example: Employee
and Address
.
Now, Employee
is accepting both parameters — city
and street
— which are needed to create an address. Instead of injecting the Address
object via the constructor, now Employee
is creating an Address
object at runtime inside the constructor. And this Employee
has a private inner class of Address
!
public class CompositeEmployee {
private final int id;
private final String name;
private final Address address;
public CompositeEmployee(int id, String name, String street, String city) {
this.id = id;
this.name = name;
this.address = new Address(street, city);
}
@Override
public String toString() {
return "CompositeEmployee{" +
"id=" + id +
", name='" + name + '\'' +
", address=" + address +
'}';
}
private static class Address {
private final String street;
private final String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"street='" + street + '\'' +
", city='" + city + '\'' +
'}';
}
}
}
Client code:
public static void main(String[] args) {
CompositeEmployee e = new CompositeEmployee(1, "John", "street 1", "city 1");
System.out.println(e);
}
Can the
Address
survive if theEmployee
is destroyed? If theEmployee
class is deleted? No! Since it’s a private inner class, it’s not accessible to the outer world. Clients cannot create independent objects ofAddress
. It implies thatAddress
is tightly coupled with theEmployee
. So, we have implemented Composition using Java!
Summary
- Aggregation: Loose coupling, component can exist independently.
- Composition: Strong coupling, component cannot exist independently.
These are common interview questions! Try to understand with real-world scenarios to make your life easier.