B3.2.4 Explain the role of composition and aggregation in class relationships.
• How to design objects by leveraging smaller component objects through composition and aggregation
• That aggregation implies that the subcomponents can function independently of the aggregating class, while in composition, the subcomponents are tightly coupled and cannot exist outside the aggregating class
The big idea
In object-oriented programming, composition and aggregation describe “has-a” relationships between classes. Both mean that one class contains objects of another class, but they differ in how tightly connected those parts are.
- Aggregation – a loose relationship: the “whole” contains the “parts,” but the parts can exist independently.
- Composition – a tight relationship: the “whole” owns the “parts,” and the parts cannot exist without it.
Knowing the difference is important for designing flexible, maintainable systems.
Composition
Definition:
A strong form of association where the contained objects’ lifecycle is bound to the containing object’s lifecycle. If the container is destroyed, its components are destroyed too.
Key characteristics:
- Tightly coupled
- The part cannot exist without the whole
- Represented in UML with a filled diamond at the container’s end
Example (Python):
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
class Car:
def __init__(self, make, horsepower):
self.make = make
self.engine = Engine(horsepower) # created inside Car
my_car = Car("Toyota", 150)
Here, the Engine object is created and managed entirely by Car. If Car is deleted, its Engine disappears too.
Aggregation
Definition:
A weaker form of association where the contained objects can live independently of the container. The “whole” uses or refers to the “parts,” but doesn’t necessarily own their lifecycle.
Key characteristics:
- Loosely coupled
- The part can exist without the whole
- Represented in UML with an empty diamond at the container’s end
Example (Python):
class Student:
def __init__(self, name):
self.name = name
class School:
def __init__(self, name):
self.name = name
self.students = [] # School does not create the students
def add_student(self, student):
self.students.append(student)
# Students exist independently
alice = Student("Alice")
school = School("Greenwood High")
school.add_student(alice)
Here, alice exists whether or not School exists. School simply holds a reference to her.
UML representation
- Aggregation: Empty diamond → Whole ◇── Part
- Composition: Filled diamond → Whole ◆── Part
When to use which
Use Composition when:
- The part has no meaning outside the whole (e.g., a
Heartinside aPerson). - The whole is responsible for creating and destroying the parts.
Use Aggregation when:
- The part can be shared or reused by multiple wholes (e.g.,
Teacherworking in multipleSchoolobjects). - The whole does not manage the part’s lifecycle.
Why this matters for design
- Flexibility: Aggregation allows parts to be reused in different contexts.
- Clarity: Composition signals a strong ownership relationship, making class responsibilities obvious.
- Maintenance: Understanding these relationships helps prevent unintended side effects when modifying code.