Skip to content

Class Relationships: Association, Aggregation and Composition

is-a, has-a, uses-a; association, aggregation, composition; UML symbols

Builds on OOP Notes and UML Class Diagrams: Reading and Drawing the Boxes

Stage 1intro17 min read

Classes rarely work alone. A class relationship describes how one class is linked to another, and how much it depends on it.

It answers three plain questions. Is one object a kind of the other? Does it hold the other as a part? Or does it only use the other for a moment?

This note covers the is-a / has-a / uses-a vocabulary, what each relationship means, how it is drawn in UML, how it looks in Java code, and how the same idea shows up in database tables.

New to class diagram boxes (visibility markers, interfaces, abstract classes, enums)? Read UML Class Diagrams first. This note is about the lines between the boxes.


Table of Contents

  1. The five relationships in one picture
  2. IS-A, HAS-A and USES-A
  3. UML notation: how each line is drawn
  4. Multiplicity: the numbers on the line
  5. Association
  6. Aggregation
  7. Composition
  8. Dependency (uses-a)
  9. Aggregation vs Composition: the "who calls new" test
  10. Full comparison table
  11. How these look in database tables
  12. Worked example: Library, Book, Chapter
  13. Bigger example: E-commerce order
  14. More real world examples
  15. Common mistakes
  16. How to decide which one to use
  17. Quiz with answers
  18. Cheat sheet

1. The five relationships in one picture

Think of them as a ladder. The further right you go, the tighter two classes are stuck together.

 WEAKEST                                                        STRONGEST
    |                                                                |
    v                                                                v
 Dependency  ->  Association  ->  Aggregation  ->  Composition  ->  Inheritance
  "uses-a"        "knows-a"        "has-a"          "owns-a"         "is-a"

  Method param    Field holding    Field holding    Field holding    extends /
  or local var    another object   parts that can   parts that die   implements
  only            long term        live alone       with the whole

                  \_____________ all three are HAS-A ______________/

The words under each box are the phrases people say. "Knows-a" and "owns-a" are just weaker and stronger flavours of has-a, because all three store the other object as a field. More on that in the next section.

Quick sentence for each:

RelationshipSentence you can say out loudExample
Dependency"I use it for a moment, then forget it."Printer uses Doc
Association"I keep a reference to it and can talk to it."Teacher and Student
Aggregation"It is part of me, but it was fine before me."Department, Employee
Composition"It is part of me, and it dies when I die."House, Room
Inheritance"I am a special kind of it."Dog is an Animal

Association, aggregation and composition are the three this note focuses on. Aggregation is a special case of association. Composition is a stricter case of aggregation. They are one idea getting tighter, not three separate things.

+-------------------------------------------------------+
|                     ASSOCIATION                        |
|   (any long term link between two classes)             |
|                                                        |
|    +-----------------------------------------+         |
|    |              AGGREGATION                |         |
|    |   (whole-part, parts can live alone)    |         |
|    |                                         |         |
|    |     +-----------------------------+     |         |
|    |     |        COMPOSITION          |     |         |
|    |     |  (whole-part, parts die     |     |         |
|    |     |   with the whole)           |     |         |
|    |     +-----------------------------+     |         |
|    +-----------------------------------------+         |
+-------------------------------------------------------+

2. IS-A, HAS-A and USES-A

Before the UML symbols, get this vocabulary right. Interviewers often ask "is this is-a or has-a?", so keep this framing in mind.

              +----------------------------+
              |   How is A related to B?   |
              +----------------------------+
                 |          |          |
        "A is a B"     "A has a B"   "A uses a B"
             |              |              |
             v              v              v
        INHERITANCE     ASSOCIATION     DEPENDENCY
        extends /       AGGREGATION     method
        implements      COMPOSITION     parameter

IS-A

A is-a B means A is a special kind of B. You get it with extends or implements. A gets all of B's behaviour. Anywhere the code expects a B, you can pass an A.

class Animal { }
class Dog extends Animal { }          // Dog IS-A Animal

interface PaymentMethod { void pay(double amount); }
class CreditCard implements PaymentMethod { }   // CreditCard IS-A PaymentMethod

Sanity check: say the sentence out loud. "A dog is an animal" sounds fine. "A car is an engine" sounds wrong, so Car extends Engine is wrong.

HAS-A

A has-a B means A holds B as a field and uses it to do its job. A does not extend B. This is the umbrella term: association, aggregation and composition are all has-a relationships. They differ only in how strongly A owns B.

class Car {
    private final Engine engine;    // Car HAS-A Engine
}

That is the key point people miss. Has-a is not a fourth relationship sitting next to the other three. It is the family name.

                        HAS-A
        (A holds B as a field and uses it)
                          |
      +-------------------+-------------------+
      |                   |                   |
 ASSOCIATION         AGGREGATION         COMPOSITION
 plain link          weak ownership      strong ownership
 no whole-part       part can live       part dies with
                     without the whole   the whole
      |                   |                   |
 Teacher has a       Library has a       Car has an
 Student list        Book list           Engine

So when someone says "use has-a here", they mean "hold it as a field instead of extending it". Which of the three you actually pick depends on ownership and lifecycle (who creates the part, and what happens to it when the whole is deleted). The rest of this note is about that.

USES-A

A uses-a B means A touches B only inside a method and does not keep it. That is dependency.

class ReportService {
    void print(Report report) {       // ReportService USES-A Report
        System.out.println(report.render());
    }
}

The three in one table

PhraseRelationship typeJava shapeQuestion to askExample
is-aInheritanceextends / implementsIs A a special kind of B?Dog is an Animal
has-aAssociation, aggregation, compositionB is a field inside ADoes A hold a B long term?Car has an Engine
uses-aDependencyB is a method parameter or localDoes A only borrow B briefly?Service uses a DTO

Same two classes, all three phrases

To make it concrete, here is Engine used three different ways:

// IS-A: true for a kind of engine (but Car extends Engine would be wrong)
class ElectricEngine extends Engine { }        // an electric engine IS an engine

// HAS-A: Car keeps an Engine as a field
class Car {
    private final Engine engine;

    Car(String model) {
        this.engine = new Engine(model);       // created inside, so composition
    }

    void start() {
        engine.ignite();
    }
}

// USES-A: the mechanic does not own an engine, one is handed to him per job
class Mechanic {
    void service(Engine engine) {              // parameter only
        engine.checkOilLevel();
    }
}

Why "prefer has-a over is-a"

You will hear "favour composition over inheritance". In this vocabulary it means: prefer has-a unless is-a is clearly true. Reasons:

  • You can swap a has-a part while the program runs. A Car can be given a different Engine. A subclass cannot change its parent once the code is compiled.
  • Has-a only exposes what you choose to expose. Inheritance drags in every public method of the parent, whether it makes sense or not.
  • In a deep inheritance chain, a change in the base class can break every class below it. With has-a, a change breaks far fewer classes.

Longer version with examples in CompositionVsInheritance.md.


3. UML notation: how each line is drawn

This is the part people usually forget in interviews. The shape at the end of the line tells you the relationship.

RelationshipDrawn asDiamond sits onMermaid syntax
Associationplain line, arrow if one directionnothingA --> B
Aggregationline with a hollow (empty) diamondthe wholeA o-- B
Compositionline with a filled (black) diamondthe wholeA *-- B
Dependencydashed line with open arrownothingA ..> B
Inheritancesolid line with hollow trianglethe parentA <|-- B
Implementsdashed line with hollow trianglethe interfaceA <|.. B

Drawn by hand:

Association     Teacher ------------ Student          (plain line)

Aggregation     Department <>------- Employee         (hollow diamond on Department)

Composition     House <#>----------- Room             (filled diamond on House)

Dependency      Printer - - - - - -> Document         (dashed arrow)

Inheritance     Animal <|----------- Dog              (hollow triangle on Animal)

The rule to remember: the diamond always goes on the "whole" side, the side that contains the other one. Empty diamond means it borrows the part. Filled diamond means it owns the part.

All of them together in one diagram:

Loading diagram…

4. Multiplicity: the numbers on the line

Next to each end of the line you write how many objects can be there. This is called multiplicity.

NotationMeaningExample in wordsIn Java
1exactly oneAn order has exactly one customerCustomer customer;
0..1zero or one, optionalA person may or may not have a passportPassport passport; (can be null)
*zero or moreA playlist can have any number of songsList<Song> songs;
1..*one or moreAn order must have at least one itemList<Item> items; (never empty)
0..*same as *-List<X> xs;
2..5a fixed rangeA car has 2 to 5 doorsList<Door> doors; + check

How it is written on the diagram:

   Department                        Employee
   +----------+  1            0..*   +----------+
   |          |<>--------------------|          |
   +----------+                      +----------+

Read it as: one Department has zero or more Employees.

In Mermaid you put the numbers in quotes:

Loading diagram…

5. Association

Association just means two classes know about each other. One holds a reference (a field that points to the other object) and can call its methods. There is no owner and no part. They are just linked.

Real life: a Teacher teaches Students. Neither owns the other. Delete the teacher and the students still exist.

5.1 One-to-one

One object of A is linked to exactly one object of B.

Loading diagram…
class Passport {
    private final String number;

    Passport(String number) {
        this.number = number;
    }

    String getNumber() {
        return number;
    }
}

class Person {
    private final String name;
    private Passport passport;   // one person, one passport

    Person(String name) {
        this.name = name;
    }

    void assignPassport(Passport passport) {
        this.passport = passport;
    }
}

5.2 One-to-many

One object of A is linked to many objects of B.

Loading diagram…
class Teacher {
    private final String name;
    private final List<Student> students = new ArrayList<>();

    Teacher(String name) {
        this.name = name;
    }

    void addStudent(Student student) {
        students.add(student);
    }
}

class Student {
    private final String name;

    Student(String name) {
        this.name = name;
    }
}

Notice the students are created outside the Teacher. That is the giveaway that nobody owns anybody here.

5.3 Many-to-many

Many A objects link to many B objects, both ways.

Loading diagram…
class Student {
    private final String name;
    private final List<Course> courses = new ArrayList<>();

    Student(String name) {
        this.name = name;
    }

    void enroll(Course course) {
        courses.add(course);
        course.addStudent(this);   // keep both sides in sync
    }
}

class Course {
    private final String title;
    private final List<Student> students = new ArrayList<>();

    Course(String title) {
        this.title = title;
    }

    void addStudent(Student student) {
        students.add(student);
    }
}

Warning for many-to-many: if you update only one side, the two lists disagree and you get bugs that are painful to find. Always update both sides in one method, like enroll above does.

5.4 Direction

Association can be one way or two way.

Unidirectional:  Order  ------->  Customer
                 Order knows its Customer.
                 Customer does not know its Orders.

Bidirectional:   Student <------> Course
                 Both sides hold a reference to the other.

Prefer unidirectional when you can. Fewer references means fewer things to keep in sync.


6. Aggregation

Aggregation is a whole-part relationship where the part can live on its own. The whole is the bigger thing (Department). The parts are the smaller things inside it (Employees).

The classic one: a Department has Employees. If the Department is shut down, the employees are still people. They can move to another department.

Loading diagram…
class Employee {
    private final String name;

    Employee(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}

class Department {
    private final String name;
    private final List<Employee> employees;

    // Employees are created outside and passed in.
    // The department only borrows them.
    Department(String name, List<Employee> employees) {
        this.name = name;
        this.employees = employees;
    }

    void addEmployee(Employee employee) {
        employees.add(employee);
    }
}

Showing that the part survives:

Employee rizon = new Employee("Rizon");
Employee aisha = new Employee("Aisha");

Department backend = new Department("Backend", new ArrayList<>(List.of(rizon, aisha)));

backend = null;   // department is gone

System.out.println(rizon.getName());   // still prints "Rizon", the object is alive

Signs you are looking at aggregation

  • The parts are created somewhere else and handed to the whole (constructor parameter or setter).
  • The same part object can belong to more than one whole at the same time, or move between them.
  • Deleting the whole should not delete the parts.

7. Composition

Composition is the strict version. The whole owns the parts. When the whole is destroyed, the parts go with it. A part belongs to exactly one whole and cannot be shared.

The classic one: a House has Rooms. Demolish the house and there is no "Living Room" floating around anywhere.

Loading diagram…
class Room {
    private final String name;
    private final double areaSqFt;

    Room(String name, double areaSqFt) {
        this.name = name;
        this.areaSqFt = areaSqFt;
    }

    String getName() {
        return name;
    }
}

class House {
    private final String address;
    private final List<Room> rooms = new ArrayList<>();

    House(String address) {
        this.address = address;
        // The house creates its own rooms. Nobody outside can hand them in.
        rooms.add(new Room("Living Room", 320));
        rooms.add(new Room("Bedroom", 240));
    }

    // Return copies of the data, not the Room objects themselves.
    List<String> getRoomNames() {
        return rooms.stream().map(Room::getName).toList();
    }
}

Now the rooms cannot outlive the house, because nothing outside the house ever gets a reference to a Room. When nothing refers to the House any more, Java can clean it up, and its Rooms go with it.

Signs you are looking at composition

  • The whole creates the parts itself, usually inside the constructor (new is inside).
  • The part is never handed out to the outside world, or only a copy is handed out.
  • A part cannot be shared between two wholes.
  • Deleting the whole must delete the parts, otherwise you have orphan data.

8. Dependency (uses-a)

Worth knowing because people mix it up with association. Dependency is the weakest link. Class A uses class B only inside a method, as a parameter, local variable or return type. A does not store B.

Loading diagram…
class ReportService {
    // Report is used only here, not stored as a field.
    void print(Report report) {
        System.out.println(report.render());
    }
}

Rule of thumb: field means association, parameter means dependency.


9. Aggregation vs Composition: the "who calls new" test

This is the fastest way to tell them apart in code.

QuestionAggregationComposition
Who creates the part?Someone outsideThe whole itself
Where is new Part()?In the callerInside the whole's constructor
Can the part be shared with another whole?YesNo
Does the part survive the whole?YesNo
Would you cascade-delete it in a database?NoYes

Side by side:

// AGGREGATION: parts come from outside, so they exist before and after
class Playlist {
    private final List<Song> songs;

    Playlist(List<Song> songs) {   // handed in
        this.songs = songs;
    }
}

Song song = new Song("Kesariya");
Playlist p1 = new Playlist(new ArrayList<>(List.of(song)));
Playlist p2 = new Playlist(new ArrayList<>(List.of(song)));   // same song in two playlists, fine


// COMPOSITION: parts are born inside, so they die inside
class Car {
    private final Engine engine;

    Car(String model) {
        this.engine = new Engine(model);   // created here, owned here
    }
}

Car car = new Car("Swift");
// There is no way to take this engine and put it in another Car.

10. Full comparison table

AspectAssociationAggregationComposition
Meaning"knows-a""has-a", weak"part-of", strong
StrengthGeneral linkWeak whole-partStrong whole-part
OwnershipNoneWhole holds the part but does not own itWhole owns the part
LifecycleFully independentPart survives the wholePart dies with the whole
Who creates the partAnyoneOutside the wholeThe whole itself
Can part be sharedYesYesNo
UML symbolPlain lineHollow diamondFilled diamond
MermaidA --> BA o-- BA *-- B
Java hintField, set from outsideField, injectedField, new in constructor
Database delete ruleJust remove the linkON DELETE SET NULLON DELETE CASCADE
ExampleTeacher and StudentDepartment and EmployeeHouse and Room, Car and Engine
Other examplesDoctor and PatientLibrary and Book, Team and PlayerOrder and OrderItem, Book and Chapter

11. How these look in database tables

The same three ideas show up when you design tables. This is usually the clearest way to see the difference, because the delete rule makes ownership obvious.

11.1 One-to-one association

A foreign key (FK) is a column that holds the id of a row in another table. Put one on either side and mark it unique, so no two rows can share a value.

persons                          passports
+----+---------+                 +----+------------+-----------+
| id | name    |                 | id | number     | person_id |  <- UNIQUE
+----+---------+                 +----+------------+-----------+
| 1  | Rizon   |                 | 9  | X1234567   | 1         |
| 2  | Aisha   |                 | 10 | Y7654321   | 2         |
+----+---------+                 +----+------------+-----------+
CREATE TABLE passports (
  id         BIGINT PRIMARY KEY,
  number     VARCHAR(20) NOT NULL,
  person_id  BIGINT UNIQUE REFERENCES persons(id)
);

11.2 One-to-many association or aggregation

The "many" side holds the foreign key. For aggregation the FK is nullable (it may be empty), because the employee stays alive when the department is deleted.

departments                      employees
+----+-----------+               +----+--------+---------------+
| id | name      |               | id | name   | department_id |  <- nullable FK
+----+-----------+               +----+--------+---------------+
| 1  | Backend   |               | 5  | Rizon  | 1             |
| 2  | Design    |               | 6  | Aisha  | 1             |
+----+-----------+               | 7  | Karan  | NULL          |  <- no department, still an employee
                                 +----+--------+---------------+
CREATE TABLE employees (
  id            BIGINT PRIMARY KEY,
  name          VARCHAR(100) NOT NULL,
  department_id BIGINT NULL REFERENCES departments(id) ON DELETE SET NULL
);

ON DELETE SET NULL is aggregation written in SQL. Drop the department, keep the people.

11.3 Many-to-many association

You cannot do it with two tables. You need a third table in the middle, called a join table or link table.

students                 student_course                courses
+----+--------+          +------------+-----------+    +----+----------+
| id | name   |          | student_id | course_id |    | id | title    |
+----+--------+          +------------+-----------+    +----+----------+
| 1  | Rizon  |          | 1          | 100       |    |100 | DSA      |
| 2  | Aisha  |          | 1          | 101       |    |101 | DBMS     |
+----+--------+          | 2          | 100       |    +----+----------+
                         +------------+-----------+
                          PK (student_id, course_id)
CREATE TABLE student_course (
  student_id BIGINT REFERENCES students(id),
  course_id  BIGINT REFERENCES courses(id),
  PRIMARY KEY (student_id, course_id)
);

Row (1, 100) means Rizon is in DSA. Rizon appears twice because he takes two courses. DSA appears twice because two students take it. That is many-to-many.

11.4 Composition

The child table has a NOT NULL foreign key (it can never be empty) and a cascade delete (deleting the parent row deletes its child rows). A child row cannot exist without its parent.

orders                           order_items
+----+------------+              +----+----------+----------+-----+
| id | placed_on  |              | id | order_id | sku      | qty |  <- NOT NULL FK
+----+------------+              +----+----------+----------+-----+
| 70 | 2026-08-16 |              | 1  | 70       | SHOE-42  | 1   |
+----+------------+              | 2  | 70       | SOCK-M   | 3   |
                                 +----+----------+----------+-----+

Delete order 70  ->  both item rows are deleted automatically.
CREATE TABLE order_items (
  id       BIGINT PRIMARY KEY,
  order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
  sku      VARCHAR(40) NOT NULL,
  qty      INT NOT NULL
);

Summary of the table rules

RelationshipFK lives onFK nullableDelete ruleExtra table needed
One-to-one associationEither sideYesSET NULLNo
One-to-many aggregationThe many sideYesSET NULLNo
Many-to-manyJoin tableNoDelete link rowYes
CompositionThe part sideNoCASCADENo

If you can point at a table and say "these rows are meaningless without the parent row", that is composition.


12. Worked example: Library, Book, Chapter

One class can be in different relationships at the same time. This is the standard example.

  • A Library aggregates Books. Burn down the library and the books still exist as objects, they can be moved to another library.
  • A Book composes Chapters. Delete the book and its chapters mean nothing.
Loading diagram…
import java.util.*;

// PART owned by Book. Package private, so code in other packages cannot build one.
// (Demo below is in the same package, but it never does.)
class Chapter {
    private final String title;
    private final int pages;

    Chapter(String title, int pages) {
        this.title = title;
        this.pages = pages;
    }

    String getTitle() {
        return title;
    }

    int getPages() {
        return pages;
    }
}

class Book {
    private final String title;
    private final List<Chapter> chapters = new ArrayList<>();

    Book(String title) {
        this.title = title;
    }

    // COMPOSITION: the Book creates the Chapter. The caller only sends data.
    void addChapter(String chapterTitle, int pages) {
        chapters.add(new Chapter(chapterTitle, pages));
    }

    // Never hand out the real Chapter objects or the real list.
    List<String> getTableOfContents() {
        List<String> toc = new ArrayList<>();
        for (Chapter chapter : chapters) {
            toc.add(chapter.getTitle() + " (" + chapter.getPages() + " pages)");
        }
        return toc;
    }

    String getTitle() {
        return title;
    }
}

class Library {
    private final String name;
    private final List<Book> books = new ArrayList<>();

    Library(String name) {
        this.name = name;
    }

    // AGGREGATION: the Book is created outside and only borrowed.
    void addBook(Book book) {
        books.add(book);
    }

    void removeBook(Book book) {
        books.remove(book);   // the Book object is still alive for the caller
    }
}

public class Demo {
    public static void main(String[] args) {
        Book book = new Book("Clean Code");
        book.addChapter("Meaningful Names", 22);
        book.addChapter("Functions", 30);

        Library cityLibrary = new Library("City Library");
        cityLibrary.addBook(book);

        cityLibrary.removeBook(book);

        // Book survives, because Library only aggregated it.
        System.out.println(book.getTitle());              // Clean Code
        System.out.println(book.getTableOfContents());    // chapters still there

        // Chapters cannot survive without the Book: there is no reference to them
        // anywhere outside, so when `book` goes away they go away too.
    }
}

13. Bigger example: E-commerce order

This one has all three relationships in a single diagram, which is what a real design looks like.

Loading diagram…

Why each one is what it is:

LinkTypeReason
Order to CustomerAssociationThe customer existed before the order and stays after. Nobody owns anybody.
Customer to AddressAggregationAn address book entry can be reused across orders and moved around.
Order to AddressAggregationThe order only points at an address the customer already saved.
Order to OrderItemCompositionA line item makes no sense without its order. Delete order, delete items.
Order to PaymentCompositionThe payment record belongs to that one order only.
OrderItem to ProductDependencyIt only reads product info when needed, it does not store the Product.
import java.util.*;

class Customer {
    private final String id;
    private final String name;

    Customer(String id, String name) {
        this.id = id;
        this.name = name;
    }

    String getName() {
        return name;
    }
}

class Address {
    private final String line1;
    private final String city;
    private final String pincode;

    Address(String line1, String city, String pincode) {
        this.line1 = line1;
        this.city = city;
        this.pincode = pincode;
    }
}

class OrderItem {
    private final String sku;
    private final int quantity;
    private final double unitPrice;

    // package private on purpose: other packages cannot build one, and only Order does here
    OrderItem(String sku, int quantity, double unitPrice) {
        this.sku = sku;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
    }

    double lineTotal() {
        return quantity * unitPrice;
    }
}

class Payment {
    private final String txnId;
    private final double amount;

    Payment(String txnId, double amount) {
        this.txnId = txnId;
        this.amount = amount;
    }
}

class Order {
    private final String orderId;
    private final Customer customer;          // association, passed in
    private final Address shippingAddress;    // aggregation, reused from customer's list
    private final List<OrderItem> items = new ArrayList<>();   // composition, built here
    private Payment payment;                  // composition, built here

    Order(String orderId, Customer customer, Address shippingAddress) {
        this.orderId = orderId;
        this.customer = customer;
        this.shippingAddress = shippingAddress;
    }

    // Composition: Order creates the item, caller never sees the constructor
    void addItem(String sku, int quantity, double unitPrice) {
        items.add(new OrderItem(sku, quantity, unitPrice));
    }

    void pay(String txnId) {
        this.payment = new Payment(txnId, total());
    }

    double total() {
        double sum = 0;
        for (OrderItem item : items) {
            sum += item.lineTotal();
        }
        return sum;
    }
}

Matching tables:

customers            addresses                   orders
+----+-------+       +----+-------------+        +----+----------+-------------+
| id | name  |       | id | customer_id |        | id | customer | address_id  |
+----+-------+       +----+-------------+        +----+----------+-------------+
| 1  | Rizon |       | 8  | 1           |        | 70 | 1        | 8           |
+----+-------+       +----+-------------+        +----+----------+-------------+
                      nullable FK                  customer_id NOT NULL, no cascade

order_items                                 payments
+----+----------+---------+-----+           +----+----------+--------+
| id | order_id | sku     | qty |           | id | order_id | amount |
+----+----------+---------+-----+           +----+----------+--------+
| 1  | 70       | SHOE-42 | 1   |           | 3  | 70       | 4999   |
+----+----------+---------+-----+           +----+----------+--------+
 order_id NOT NULL, ON DELETE CASCADE        order_id NOT NULL, ON DELETE CASCADE

Delete order 70 and the item and payment rows go with it. The customer and address rows stay.


14. More real world examples

PairRelationshipWhy
Car and EngineCompositionThe engine is built into that car, not shared
Human and HeartCompositionSame idea
Order and OrderItemCompositionA line item is meaningless alone
Book and ChapterCompositionChapter dies with the book
House and RoomCompositionRooms do not exist after demolition
Bank Account and TransactionCompositionA transaction belongs to one account only
Question and Options (MCQ)CompositionOptions belong to that question
Library and BookAggregationBooks can be moved to another library or sold
University and StudentAggregationA student exists before joining and after leaving
Team and PlayerAggregationPlayers get transferred between teams
Playlist and SongAggregationOne song can be in many playlists
Company and EmployeeAggregationPeople change jobs
Computer and USB MouseAggregationUnplug it and use it on another machine
Teacher and StudentAssociationThey interact, neither contains the other
Doctor and PatientAssociationSame
Driver and CarAssociationThe driver uses the car but is not part of it
Student and CourseAssociationMany-to-many, no ownership
Controller and ServiceAssociationInjected reference, both live independently
Service and DTO parameterDependencyUsed inside a method only

A useful test for the tricky ones: ask what happens to the part when you delete the whole. If the part still makes sense on its own, it is aggregation or association. If the part becomes garbage data, it is composition.

Car and Engine trips people up. In the real world you can swap an engine, so people say aggregation. But in most software models the engine object is created with the car and never shared, so it is drawn as composition. Model what your system actually does, not what is theoretically possible in a garage.


15. Common mistakes

1. Saying it is composition but leaking the parts.

Leaking means outside code gets the real parts, so it can keep them alive after the whole is gone.

// Looks like composition, but is not
class House {
    private final List<Room> rooms = new ArrayList<>();

    House() {
        rooms.add(new Room("Bedroom"));
    }

    List<Room> getRooms() {
        return rooms;   // BAD: caller can keep this list and the Rooms forever
    }
}

List<Room> escaped = new House().getRooms();
// house is gone, rooms are still alive. Ownership is broken.

Fix it by returning a copy, or by returning read-only data:

List<Room> getRooms() {
    return List.copyOf(rooms);   // caller cannot add or remove
}

Even better, do not return the Room objects at all, return the strings or a small record (a short, read-only data class).

2. Using inheritance where composition fits.

class Car extends Engine is wrong. A car is not an engine, a car has an engine. If your sentence sounds silly with "is a", use composition. See CompositionVsInheritance.md.

3. Bidirectional links that drift apart.

If student.enroll(course) adds to the student list but forgets course.addStudent(student), the two sides disagree. Always change both in one method.

4. Cascade delete on an aggregation.

Putting ON DELETE CASCADE on employees.department_id means deleting a department deletes the people. That is a data loss bug, not a design choice.

5. Calling everything association because it is safe.

It is technically true, since aggregation and composition are both associations. But in a design interview it shows you cannot tell ownership apart, which is the whole point of the question.


16. How to decide which one to use

Does class A only use B inside a method, without storing it?
    YES -> Dependency
    NO  |
        v
Is A a special kind of B?
    YES -> Inheritance (extends / implements)
    NO  |
        v
Is B a part of A? (would you say "A has a B" in a whole-part sense)
    NO  -> Association
    YES |
        v
If A is deleted, should B be deleted too?
    NO  -> Aggregation   (hollow diamond, o--, FK nullable, SET NULL)
    YES -> Composition   (filled diamond, *--, FK not null, CASCADE)

17. Quiz with answers

Q1. Which best describes association in OOP?

Answer: a general link between two classes. Objects of one class hold references to objects of the other and can use them. Nobody owns anybody. It can be one-to-one, one-to-many or many-to-many, and each object has its own lifecycle. In code, one class has a field that refers to the other.

Q2. In an aggregation relationship, what happens to the part objects when the whole is destroyed?

Answer: nothing. They keep existing. Aggregation is a weak link, so the parts have their own lifecycle. Delete a Department and the Employee objects are still there.

Q3. Which of the following is true about composition?

  • The composed object and its parts can exist independently. (Wrong, that is aggregation.)
  • Composition is a form of association where the whole owns the parts, and the parts are destroyed if the whole is destroyed. (Correct.)
  • Composition is a weak form of aggregation where parts are not tightly bound. (Wrong, it is the strong form. Composition is the stricter one, aggregation is the weaker one.)
  • Composition is the same as inheritance. (Wrong. Composition is "has-a", inheritance is "is-a".)

Q4. Main difference between aggregation and composition?

Answer: lifecycle and ownership. In aggregation the whole holds parts that can live without it and can be shared. In composition the whole owns its parts, creates them, and the parts die with it. In UML it is a hollow diamond versus a filled diamond, and in a database it is ON DELETE SET NULL versus ON DELETE CASCADE.

Q5. Which is an example of composition?

  • A library and the books it contains. (Aggregation. Books can move to another library.)
  • A university and the students it enrolls. (Aggregation. Students exist after graduating.)
  • A playlist and its songs. (Aggregation. The same song can be in many playlists.)
  • A car and its engine. (Composition. The engine is created with that car and is not shared.)

The pattern in all four options: only in the car and engine case does the part stop making sense once the whole is gone.

Q6. Is "has-a" the same as aggregation?

Answer: no. Has-a is the general phrase for "A holds B as a field". Association, aggregation and composition are all has-a relationships. Aggregation is only the middle one, where the part can live without the whole. If an interviewer asks "is this is-a or has-a", they are asking whether to use inheritance or a field. If they then ask "which kind of has-a", they are asking about ownership and lifecycle.

Q7. class Car extends Engine or class Car { private Engine engine; }?

Answer: the second one. A car is not a kind of engine, a car has an engine. Use is-a only when the child can stand in for the parent everywhere. Here it cannot, so it is has-a, and since the car creates and owns the engine it is specifically composition.


18. Cheat sheet

DEPENDENCY   uses-a      A ..> B     method parameter only        Printer, Document
ASSOCIATION  has-a       A --> B     field, both independent      Teacher, Student
AGGREGATION  has-a       A o-- B     field, part passed in        Library, Book
COMPOSITION  has-a       A *-- B     field, part created inside   Car, Engine
INHERITANCE  is-a        A <|-- B    extends / implements         Animal, Dog

HAS-A is the family name for the middle three. They are all "B is a field
inside A", they only differ in who owns the lifecycle:
    association -> nobody owns anybody
    aggregation -> A holds B, B survives A
    composition -> A owns B, B dies with A

Diamond goes on the WHOLE.  Empty diamond = borrowed.  Filled diamond = owned.

Java tell:   new Part() outside the whole  -> aggregation
             new Part() inside the whole   -> composition

SQL tell:    ON DELETE SET NULL            -> aggregation
             ON DELETE CASCADE             -> composition

Multiplicity: 1   exactly one
              0..1 optional
              *    zero or more
              1..* at least one

See also

In this topic

Done with this topic?

How well do you know it?

PreviousUML Class Diagrams: Reading and Drawing the Boxes