Association, Aggregation, Composition and Dependency
Learning Objectives
Distinguish association, aggregation, composition and dependency by ownership and lifetime.
Express each relationship in C++ code and in UML notation.
Read multiplicity and navigability.
Model a small domain with the right relationships.
Prerequisites: Modules 2–8.
Intuition and Definition
Typical C++ form A *uses* B temporarily (parameter, local) void print(const Printer& p) dashed arrow ..> A *knows* B (long-term link) non-owning pointer/reference member solid line -->
A *has* B as a part, but B can outlive A non-owning pointer, shared_ptr, IDs hollow diamond o-- A *owns* B; B dies with A B bounded by A member object or unique_ptr filled diamond *--
Multiplicity says how many: 1, 0..1, * (many), 1..*. Navigability says which side knows the other (an arrowhead).
UML practice varies: many teams avoid aggregation entirely because its semantics are vague — when in doubt, state ownership and lifetime in words.
Why It Matters
Ownership decides who deletes what, what happens when an object is removed, and how objects are serialized and tested. Getting it wrong produces leaks, dangling pointers and confusing models.
Syntax and Language Notes
In garbage-collected languages (Java, C#, Python, TypeScript) every member is a reference, so composition is a *design convention*: the whole creates the part, never shares it, and releases it when the whole is discarded.
class Engine {};
class Driver {};
class Garage {};
class Car {
Engine engine_; // composition: created and destroyed with the Car
Driver* driver_ = nullptr; // association: Car knows a Driver it does not own
public:
void parkIn(Garage& g) { (void)g; } // dependency: used only during the call
};Complete Example
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Part {
std::string name;
explicit Part(std::string n) : name(std::move(n)) { std::cout << " + " << name << "\n"; }
~Part() { std::cout << " - " << name << "\n"; }
};
struct Student {
std::string name;
explicit Student(std::string n) : name(std::move(n)) {}
};
class Logger { // used only as a dependency
public:
void write(const std::string& s) const { std::cout << " log: " << s << "\n"; }
};
class Course { // aggregation: students exist independently of the course
std::string title_;
std::vector<const Student*> students_;
public:
explicit Course(std::string t) : title_(std::move(t)) {}
void enrol(const Student& s) { students_.push_back(&s); }
void report(const Logger& log) const { // dependency on Logger
log.write(title_ + " has " + std::to_string(students_.size()) + " students");
}
};
class Car { // composition: the car owns its engine and wheels
Part engine_{"engine"};
std::vector<std::unique_ptr<Part>> wheels_;
public:
Car() { for (int i = 1; i <= 2
…composition:
+ engine
+ wheel 1
+ wheel 2
(car in use)
- wheel 2
- wheel 1
- engine
aggregation + dependency:
log: OOP has 2 students
students still alive: Ana, RajStep-by-Step Walkthrough
Creating the Car creates its parts; leaving the scope destroys the car and every part (members in reverse order: wheels vector, then engine).
The Course stores non-owning pointers to students. Destroying the course does not destroy students — aggregation.
report receives a Logger only for the duration of the call — a dependency.
classDiagram
Car *-- "1" Engine
Car *-- "2..*" Wheel
Course o-- "0..*" Student
Course ..> Logger : uses
Driver "1" --> "0..1" Car : drivesCommon Mistakes
Raw owning pointers (Part* p = new Part) for composition — use members or unique_ptr.
Aggregation via non-owning pointers to objects that die first — dangling pointers. Document lifetimes or use IDs/weak_ptr.
Bidirectional links everywhere (Course ↔ Student), which must be kept in sync and create cycles.
Modelling every association as inheritance ("Student extends Person extends Course…").
Trade-offs and Alternatives
Identifiers (StudentId) instead of pointers make aggregation robust across persistence and threads, at the cost of a lookup.
shared_ptr expresses shared ownership but hides who really owns what and can form cycles (break with weak_ptr, Module 13).
Real-World Use
Modelling exercises — choose the relationship:
University — Department composition (departments don't exist without the university) Department — Professor aggregation/association (professors can move) Order — OrderLine Order — Customer association (by customer id) ParkingLot — ParkingSpot Ticket — Vehicle ECU — CAN driver composition of the driver instance, dependency on the bus abstraction
Practice
Q1. (Easy) A function takes a Printer& parameter only to print. Which relationship?
Answer: Dependency.
Q2. (Medium) Give the UML symbol and C++ form for composition.
Answer: A filled diamond on the whole's side; in C++ a member object or std::unique_ptr member.
Q3. (Advanced) A playlist holds songs that also belong to a library. Removing the playlist must not delete songs; deleting a song from the library must remove it from all playlists. Propose a design.
Answer: The Library owns songs (composition, e.g. std::map<SongId, Song>). Playlists aggregate songs by SongId. When a song is deleted, the library publishes a "song removed" event (Observer, Module 41) and playlists drop that id — or playlists resolve ids lazily and skip missing ones. No playlist holds a pointer that could dangle.
Interview Question
Q1. "Explain aggregation vs composition with an example and say how you'd implement each in C++."
Answer: Composition: a House and its Rooms — rooms are created with the house and destroyed with it; implement as member objects or std::unique_ptr<Room> members. Aggregation: a Team and its Players — players exist before and after the team; implement as non-owning references/pointers or IDs, with an explicit statement of who owns the players (e.g. a League registry). The deciding questions are *who creates it, who destroys it, and can it be shared?*
Quick Quiz
Q1. Which relationship ties the part's lifetime to the whole?
Answer: Composition.
Q2. What does 0..* mean?
Answer: Zero or more.
Q3. Which UML arrow is dashed with an open head?
Answer: Dependency.
Summary
Dependency (uses), association (knows), aggregation (has, independent lifetime), composition (owns, shared lifetime).
Ownership and lifetime are the deciding questions; express them clearly in code.
Related: Module 13 (smart pointers), Module 23 (UML class diagrams), Module 74 (DDD aggregates).