Relationships Between Classes

Lesson 0014 — 30 min read

In object-oriented design, classes interact to model real-world systems. Understanding how objects connect through Association, Aggregation, and Composition is essential for creating clean, modular, and maintainable software architectures.

Overview of Class Relationships

Relationships between classes generally fall into three core categories based on ownership and lifecycle coupling:

1. Association

Association defines a connection or communication link between two independent classes. Objects collaborate through pointers, references, or collections without owning each other's lifecycles.

Cardinality Types

import java.util.List;

class Passport {
    private String passportNumber;
    public Passport(String number) { this.passportNumber = number; }
}

class Person {
    private String name;
    private Passport passport; // One-to-One Association

    public Person(String name, Passport passport) {
        this.name = name;
        this.passport = passport;
    }
}
#include <iostream>
#include <string>
using namespace std;

class Passport {
private:
    string passportNumber;
public:
    Passport(string number) : passportNumber(number) {}
};

class Person {
private:
    string name;
    Passport* passport; // One-to-One Association via pointer
public:
    Person(string name, Passport* p) : name(name), passport(p) {}
};
class Passport:
    def __init__(self, number):
        self.passport_number = number

class Person:
    def __init__(self, name, passport: Passport):
        self.name = name
        self.passport = passport # One-to-One Association

2. Aggregation (Weak "Has-A")

Aggregation is a specialized form of association that represents a whole-part ("has-a") relationship. The bond is weak: if the container ("whole") is destroyed, the contained items ("parts") continue to exist independently.

Example: A Department contains multiple Employee objects. If the Department is deleted, the Employee objects still exist.

import java.util.List;

class Employee {
    private String name;
    public Employee(String name) { this.name = name; }
}

class Department {
    private List<Employee> employees; // Aggregation

    // Employees are created outside and passed in
    public Department(List<Employee> employees) {
        this.employees = employees;
    }
}
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Employee {
private:
    string name;
public:
    Employee(string name) : name(name) {}
};

class Department {
private:
    vector<Employee> employees; // Aggregation
public:
    Department(vector<Employee> employees) : employees(employees) {}
};
from typing import List

class Employee:
    def __init__(self, name):
        self.__name = name

class Department:
    def __init__(self, employees: List[Employee]):
        self.__employees = employees # Aggregation (employees created externally)

3. Composition (Strong "Part-Of")

Composition is a strict form of aggregation representing a part-of relationship. The container owns the child objects. If the container ("whole") is destroyed, all its child components ("parts") are automatically destroyed with it.

Example: A House consists of Room objects. The Room instances are created inside the House constructor and cannot exist without the House.

import java.util.ArrayList;
import java.util.List;

class Room {
    private String name;
    public Room(String name) { this.name = name; }
}

class House {
    private List<Room> rooms; // Composition

    public House() {
        rooms = new ArrayList<>();
        // Rooms created internally — lifecycle tied to House
        rooms.add(new Room("Living Room"));
        rooms.add(new Room("Bedroom"));
    }
}
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Room {
private:
    string name;
public:
    Room(string name) : name(name) {}
};

class House {
private:
    vector<Room> rooms; // Composition
public:
    House() {
        // Rooms constructed inside House — destroyed when House is destroyed
        rooms.push_back(Room("Living Room"));
        rooms.push_back(Room("Bedroom"));
    }
};
class Room:
    def __init__(self, name):
        self.__name = name

class House:
    def __init__(self):
        self.__rooms = []
        # Rooms constructed inside House — lifecycle tied to House
        self.__rooms.append(Room("Living Room"))
        self.__rooms.append(Room("Bedroom"))

Multiple Relationships in Real Systems

Classes in real software often participate in multiple relationship types simultaneously:

Comparison Summary

Aspect Association Aggregation Composition
Relationship Type General interaction ("uses-a") Weak whole-part ("has-a") Strong whole-part ("part-of")
Ownership No ownership Contains, but does not own lifecycle Strict ownership of child lifecycle
Lifecycle Dependency Independent lifecycles Contained object outlives container Contained object dies with owner
Real-World Example Teacher & Student Department & Employee House & Room / Car & Engine

Which relationship type exists between a House and its Room instances?

What distinguishes Aggregation from Composition?

Notes

Class Relationships :-

Association ⇒ General interaction ("uses-a") between independent objects

Aggregation ⇒ Weak whole-part ("has-a") ; independent lifecycles

Composition ⇒ Strong whole-part ("part-of") ; owner controls child lifecycle

Association Types :-

a) One-to-One ⇒ PersonPassport

b) One-to-Many ⇒ TeacherStudent

c) Many-to-Many ⇒ StudentCourse

Aggregation vs Composition :-

Aggregation ⇒ Parts created externally and passed in (eg Department has Employee list)

Composition ⇒ Parts created inside container constructor (eg House creates Room list)

Lifecycle rule ⇒ Destroying container destroys parts in composition, NOT in aggregation

Primary source: Oracle Java Tutorials — Interfaces & Inheritance & cppreference — Class Design Patterns. Ask me anything that's unclear.

← Prev: Inner Classes Next: Object Cloning →

Questions? Ask your agent — you can follow up on any concept, quiz answer, or how this applies to a specific coding problem. Review the Java glossary or syntax quick reference for a compressed overview.