bigonotes
CS ConceptsOOP

Relationships: Association, Aggregation, Composition

How classes relate (is-a vs has-a), and how to tell association, aggregation and composition apart in UML and code.

3 min readLast updated OOP

Classes rarely stand alone. In a design you need to say how they relate: is one a kind of another, or does one have or use another? Getting this right drives your class diagram, who creates which object, and what gets cleaned up when something is deleted.

Is-a vs has-a#

Every relationship between two classes is one of two kinds:

  • Generalization (inheritance) is an is-a relationship. Penguin is a Bird. The child inherits from the parent.
  • Association is a has-a / uses-a relationship. One class holds a reference to, or works with, another.

Association is the umbrella term. It has two stronger, more specific forms based on ownership and lifecycle:

Relationship Meaning Lifecycle UML symbol
Association "uses / knows about" Fully independent Plain line (optionally an arrow)
Aggregation Weak has-a: a whole made of parts that can live on their own Part survives if the whole is destroyed Hollow diamond ◇ at the whole
Composition Strong has-a: the part belongs to exactly one whole Part is destroyed with the whole Filled diamond ◆ at the whole
Association, aggregation and composition side by side with their UML line styles and lifecycle rule

Aggregation (weak has-a)#

The objects are linked but have separate lives. Destroy the owner and the part survives.

Examples:

  • Car and Driver: a car has a driver, but if the car is scrapped the driver walks away and gets another car.
  • Classroom and Teacher: demolish the classroom and the teacher moves to another room.
  • School and Student: if the school closes, the students transfer elsewhere.

In code, the part is usually created outside and passed in (constructor or setter). Because someone else created it, it outlives the owner.

class Driver:
    def __init__(self, name: str = "John"):
        self.name = name


class Car:
    def __init__(self, driver: Driver):
        # Aggregation: Car refers to a Driver it didn't create.
        # The driver is passed in from outside (dependency injection).
        self._driver = driver


john = Driver()
car = Car(john)
del car           # the Car is gone...
print(john.name)  # ...but john is still referenced and alive: John

Composition (strong has-a)#

The part cannot meaningfully exist without the whole. Destroy the owner and the part goes with it.

Examples:

  • Body and Heart: a heart is part of exactly one body.
  • Car and its state: a car's state object (engine running, parked) only makes sense while that car exists.
  • House and Rooms: demolish the house and its rooms are gone.

In code, the owner usually creates the part itself and never hands it out to be shared, so it controls the lifecycle.

class Heart:
    def __init__(self):
        self.beats_per_minute = 70


class Body:
    def __init__(self):
        self._heart = Heart()  # created inside; no one else holds it


body = Body()
del body  # nothing else refers to the Heart, so it is freed too

Plain association#

Sometimes two classes simply work together with no ownership at all, e.g. a Teacher teaches many Students and a student has many teachers. Neither owns the other; that's plain association.

class Student:
    def __init__(self, name: str):
        self.name = name


class Teacher:
    def grade(self, student: Student) -> None:
        # Uses a Student for the duration of the call, owns nothing
        print(f"Grading {student.name}")

Telling them apart#

Ask these questions about any pair of classes:

Question Association Aggregation Composition
Is there a whole/part? No Yes Yes
Can the part be shared? n/a Yes No, one owner
Destroy owner, part...? Unaffected Survives Destroyed
Code hint Method parameter or loose reference Object passed into constructor/setter Part() created inside the owner

In interviews, prefer composition over inheritance for code reuse: "has-a" relationships are easier to change at runtime and avoid fragile class hierarchies.

Key takeaways#

  • Inheritance is is-a; association, aggregation and composition are has-a/uses-a.
  • Aggregation: the part lives independently (hollow diamond).
  • Composition: the part lives and dies with the whole (filled diamond).
  • Passed in from outside usually means aggregation; created inside usually means composition.
  • Favor composition over inheritance when you just need to reuse behavior.

Found a mistake? Tell me on LinkedIn ↗

Further reading#