Aggregation
IB Syllabus: B3.1 – Implement aggregation (has-a relationship) between classes.
Table of Contents
- Key Concepts
- Worked Examples
- Quick Check
- Trace Exercise
- Spot the Error
- Predict the Output
- Practice Exercises
- Connections
Key Concepts
What is Aggregation?
Aggregation is a relationship where one object contains another object as an attribute. This is a “has-a” relationship – a Library has Book objects, a School has Student objects, a Car has an Engine.
The containing object (the “whole”) stores a reference to the contained object (the “part”) and can call its methods. This lets you build complex systems from simpler components.
public class Address {
private String street;
private String city;
private String country;
public Address(String street, String city, String country) {
this.street = street;
this.city = city;
this.country = country;
}
public String getCity() { return city; }
public String getCountry() { return country; }
}
public class Student {
private String name;
private Address homeAddress; // aggregation -- Student HAS-A Address
public Student(String name, Address homeAddress) {
this.name = name;
this.homeAddress = homeAddress;
}
public String getName() { return name; }
// Delegation -- calling a method on the contained object
public String getCity() {
return homeAddress.getCity();
}
}
Address addr = new Address("123 Main St", "Bangkok", "Thailand");
Student s = new Student("Alice", addr);
System.out.println(s.getName() + " lives in " + s.getCity());
Output:
Alice lives in Bangkok
The Student object does not know how addresses work internally – it just calls homeAddress.getCity(). This is delegation – passing responsibility to the contained object.
Aggregation vs Inheritance
These are the two fundamental ways classes relate to each other. Using the wrong one causes design problems.
| Aggregation (has-a) | Inheritance (is-a) | |
|---|---|---|
| Relationship | One object contains another | One class extends another |
| Example | A Car has an Engine | A Dog is an Animal |
| Code | private Engine engine; | class Dog extends Animal |
| Coupling | Loose – parts are independent | Tight – child depends on parent |
| Polymorphism | Not supported | Supported |
| Flexibility | Parts can be swapped at runtime | Fixed at compile time |
To decide between aggregation and inheritance, ask: “Is A a type of B?” If yes, use inheritance (Dog IS-A Animal). If no, ask: “Does A contain B?” If yes, use aggregation (Car HAS-A Engine). Never use inheritance just to reuse code – use aggregation instead.
Arrays of Objects
The most common form of aggregation in IB exams is a class that manages an array of objects. This is where the exam method patterns (filter, accumulate, sort) apply.
public class Library {
private String name;
private Book[] books;
private int bookCount;
public Library(String name, int capacity) {
this.name = name;
this.books = new Book[capacity];
this.bookCount = 0;
}
public void addBook(Book book) {
if (bookCount < books.length) {
books[bookCount] = book;
bookCount++;
}
}
// Filter pattern: find books matching a condition
public void displayAvailable() {
for (int i = 0; i < bookCount; i++) {
if (books[i] != null && !books[i].isOnLoan()) {
System.out.println(books[i].getTitle());
}
}
}
// Accumulate pattern: count items matching a condition
public int countByAuthor(String author) {
int count = 0;
for (int i = 0; i < bookCount; i++) {
if (books[i] != null && books[i].getAuthor().equals(author)) {
count++;
}
}
return count;
}
}
Always check for
nullbefore calling methods on array elements. Even with abookCounttracker, defensive null checks protect against bugs where elements are removed or the array is sparsely populated. This null check earns a mark in nearly every IB exam method question.
Delegation
When the “whole” object needs information from a “part”, it delegates by calling a method on the contained object rather than accessing the data directly:
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
// Delegation: Car asks Engine for its horsepower
public int getHorsepower() {
return engine.getHorsepower();
}
// Delegation with logic
public boolean isPowerful() {
return engine.getHorsepower() > 200;
}
}
The Car does not store horsepower itself – it asks its Engine. If the engine is replaced, the car automatically reports the new horsepower. This keeps each class responsible for its own data.
Nested Object Access
Sometimes you need to access data several levels deep. Each step calls a method on the returned object:
// lawyer.getCases()[i].getDaysFromStart()
// ↑ get the array ↑ get one case ↑ get its duration
This pattern appears frequently in IB P2 questions – you traverse from a container to its parts to the parts’ attributes, chaining getter calls.
Worked Examples
Example 1: Design with Aggregation
Scenario: A Gym has members. Each member has a name and membership type. The gym needs to count premium members and find a member by name.
UML:
+----------------------------------+ +---------------------------+
| Gym | | Member |
+----------------------------------+ +---------------------------+
| - name: String | | - name: String |
| - members: Member[] | | - type: String |
| - memberCount: int | +---------------------------+
+----------------------------------+ | + Member(String, String) |
| + Gym(String, int) |◇------| + getName(): String |
| + addMember(Member): void | | + getType(): String |
| + countPremium(): int | | + isPremium(): boolean |
| + findByName(String): Member | +---------------------------+
+----------------------------------+
Java:
public class Member {
private String name;
private String type;
public Member(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() { return name; }
public String getType() { return type; }
public boolean isPremium() { return type.equals("premium"); }
}
public class Gym {
private String name;
private Member[] members;
private int memberCount;
public Gym(String name, int capacity) {
this.name = name;
this.members = new Member[capacity];
this.memberCount = 0;
}
public void addMember(Member m) {
if (memberCount < members.length) {
members[memberCount] = m;
memberCount++;
}
}
// Accumulate pattern
public int countPremium() {
int count = 0;
for (int i = 0; i < memberCount; i++) {
if (members[i] != null && members[i].isPremium()) {
count++;
}
}
return count;
}
// Find-by-attribute pattern
public Member findByName(String name) {
for (int i = 0; i < memberCount; i++) {
if (members[i] != null && members[i].getName().equals(name)) {
return members[i];
}
}
return null; // not found
}
}
Example 2: Selection Sort on Object Array
Sort members alphabetically by name (ascending):
public void sortByName() {
for (int i = 0; i < memberCount - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < memberCount; j++) {
if (members[j].getName().compareTo(members[minIndex].getName()) < 0) {
minIndex = j;
}
}
// Swap entire objects
Member temp = members[i];
members[i] = members[minIndex];
members[minIndex] = temp;
}
}
When sorting objects by a String attribute, use
.compareTo()instead of<or>. It returns a negative number if the first string comes before the second alphabetically, zero if equal, and positive if it comes after.
Quick Check
Q1. What type of relationship is aggregation?
Q2. What is delegation in OOP?
Q3. A Car contains an Engine. Should this be aggregation or inheritance?
Q4. What does findByName("Charlie") return if Charlie is not in the array?
Trace Exercise
Trace the Gym class methods. A gym has 3 members: Alice (premium), Bob (basic), Charlie (premium).
Trace: Aggregation Methods
Gym g = new Gym("FitLife", 10);
g.addMember(new Member("Alice", "premium"));
g.addMember(new Member("Bob", "basic"));
g.addMember(new Member("Charlie", "premium"));| Method call | Result |
|---|---|
g.countPremium() | |
g.findByName("Bob").getType() | |
g.findByName("Dave") |
Spot the Error
This method crashes with a NullPointerException. Click the buggy line, then pick the fix.
Pick the fix:
Predict the Output
What does countPremium() return for a gym with members Alice (premium), Bob (basic), Charlie (premium)?
Practice Exercises
Core
-
Design two classes – Create a
Courseclass (name, teacher, maxStudents) and aSchoolclass that manages an array of Courses. WriteaddCourse(),findCourse(String name), andcountAvailable()(courses with fewer than maxStudents enrolled). - Aggregation identification – For each pair, state whether the relationship is aggregation (has-a) or inheritance (is-a):
- (a) Playlist and Song
- (b) ElectricCar and Car
- (c) Hospital and Doctor
- (d) Rectangle and Shape
- Read and trace – Given the Library and Book classes from this page, trace what happens when you add 3 books, check out one of them, then call
countByAuthor().
Extension
-
Selection sort – Add a
sortByPrice(Product[] products, int count)method that sorts products by price in descending order (highest first). Use selection sort and getter methods. -
Nested access – A
Departmenthas an array ofEmployeeobjects. EachEmployeehas aSalaryobject withgetBase()andgetBonus()methods. Write a methodtotalPayroll()inDepartmentthat sumsbase + bonusfor all employees.
Challenge
- Full system – Design and implement a
Hotelsystem withRoomandGuestclasses. A Hotel manages rooms. Each room has a number, type (“single”/”double”), price, and an optional Guest. Implement: check-in (assign guest to room), check-out (remove guest), find available rooms by type, and calculate total revenue from occupied rooms.
Connections
- Prerequisites: Classes and Objects – creating and using objects
- Prerequisites: UML Class Diagrams – the diamond (◇) notation for has-a
- Prerequisites: Object References – null checks, reference behaviour
- Next: Inheritance – the other fundamental relationship (is-a)
- Forward: OOP Exam Patterns – the filter, accumulate, and sort patterns used here are exam essentials