UML Class Diagrams

Learning Objectives

Read and draw class boxes with attributes, operations, visibility, static and abstract members.

Use all relationship notations: inheritance, realization, dependency, association, aggregation, composition, multiplicity and navigability.

Model small domains as class diagrams.

Prerequisites: Modules 4, 7, 9.

Intuition and Definition

A class diagram shows types and their static relationships — the structure of the code, not its run-time behaviour.

+ public, - private, # protected, ~ package/internal underlined (in Mermaid: trailing $) Abstract member/class italics (in Mermaid: trailing * or <<abstract>>) - balance: Money + deposit(amount: Money): void Inheritance (generalization) solid line, hollow triangle dashed line, hollow triangle implements an interface solid line (arrow = navigability) hollow diamond at the whole has, independent lifetime filled diamond at the whole owns, shared lifetime

Multiplicities sit at the line ends: 1, 0..1, *, 1..*.

Why It Matters

Class diagrams are the fastest way to discuss a design on a whiteboard, review a pull request's structure, or document a module boundary. They are a communication tool — draw the parts that matter for the question at hand.

Syntax and Language Notes

Diagrams-as-code (Mermaid, PlantUML) live next to the source and are reviewed like code:

classDiagram
  class Account {
    <<abstract>>
    -balance : Money
    +deposit(amount : Money)
    +withdraw(amount : Money)*
  }
  class SavingsAccount {
    -rate : double
    +addInterest()
  }
  class Auditable {
    <<interface>>
    +auditTrail() string
  }
  Account <|-- SavingsAccount
  Auditable <|.. Account
  Customer "1" --> "1..*" Account : owns

Complete Example

A library system: the diagram, then code that matches it exactly.

classDiagram
  class Library {
    -books : map~Isbn, Book~
    -members : vector~Member~
    +addBook(book)
    +lend(isbn, member) Loan
  }
  class Book {
    -isbn : string
    -title : string
    -copies : int
  }
  class Member {
    -name : string
    +canBorrow() bool
  }
  class Loan {
    -due : int
  }
  class Notifier {
    <<interface>>
    +remind(member, text)
  }
  Library *-- "0..*" Book
  Library o-- "0..*" Member
  Loan --> "1" Book
  Loan --> "1" Member
  Library ..> Notifier : uses
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <vector>

struct Notifier { virtual ~Notifier() = default; virtual void remind(const std::string& who, const std::string& text) = 0; };

class Book {
    std::string isbn_, title_;
    int copies_;
public:
    Book(std::string i, std::string t, int c) : isbn_(std::move(i)), title_(std::move(t)), copies_(c) {}
    bool available() const { return copies_ > 0; }
    void checkOut() { --copies_; }
    const std::string& title() const { return title_; }
};

class Member {
    std::string name_;
    int loans_ = 0;
public:
    explicit Member(std::string n) : name_(std::move(n)) {}
    bool canBorrow() const { return loans_ < 3; }
    void addLoan() { ++loans_; }
    const std::string& name() const { return name_; }
};

struct Loan { const Book* book; const Member* member; int due; };      // associations

class Library {
    std::map<std::string, Book> books_;              // composition: the library owns its books
public:
    void addBook(const std::string& isbn, const std::string& title, int copies) { books_.emplace(isbn, Book(isbn, title, copies)); }
    std::optional<Loan> lend(const std::string& isbn,
…
to Ana: return 'Design Patterns' by day 114
Ana's loan: due day 114
Raj's loan: refused (no copies left)

Step-by-Step Walkthrough

Library *-- Book: the map stores Book objects by value — composition.

Library o-- Member: members are created in main and merely used by the library — aggregation.

Loan --> Book, Loan --> Member: the loan holds non-owning pointers — navigable associations from Loan.

Library ..> Notifier: the notifier is a parameter only — dependency.

Common Mistakes

Drawing every class and every getter — diagrams should answer a question.

Diamonds on the wrong end: the diamond goes on the *whole* (the owner).

Using inheritance arrows for "uses" or composition for shared parts.

Diagrams that drift from code — keep them small, as code (Mermaid/PlantUML), and review them in pull requests.

Trade-offs and Alternatives

Class diagrams show structure but not behaviour; pair them with sequence diagrams (Module 24). For data-heavy systems, ER diagrams describe persistence better.

Real-World Use

Modelling exercises: library, e-commerce (Customer, Order, OrderLine, Product, Payment), hospital (Patient, Admission, Ward, Doctor), parking lot (Lot, Level, Spot, Ticket, Vehicle), banking (Customer, Account, Transaction), vehicle cluster (Cluster, Sensor, Warning, DisplayMode).

Practice

Q1. (Easy) What does # mean in front of an attribute?

Answer: Protected visibility.

Q2. (Medium) Draw (in Mermaid) Order composed of 1..* OrderLine, and OrderLine associated with exactly one Product.

Q3. (Advanced) How would you show that Notifier is an interface implemented by EmailNotifier, and that Library only uses it in one method?

Answer: <<interface>> Notifier, a realization arrow Notifier <|.. EmailNotifier (dashed line, hollow triangle), and a dependency Library ..> Notifier (dashed open arrow) rather than an association, since there's no stored reference.

classDiagram
  Order *-- "1..*" OrderLine
  OrderLine --> "1" Product

Interview Question

Q1. "What's the difference between realization and generalization in UML?"

Answer: Generalization (solid line, hollow triangle) is class inheritance — the subclass inherits structure and behaviour. Realization (dashed line, hollow triangle) means a class *implements* an interface's contract without inheriting implementation. In C++ both are written with : public, so the diagram makes the design intent clearer than the code.

Quick Quiz

Q1. Which side gets the composition diamond?

Answer: The whole/owner.

Q2. How is a static member shown?

Answer: Underlined (Mermaid: trailing $).

Q3. What does 1..* mean?

Answer: One or more.

Summary

Class boxes show attributes and operations with visibility; relationships show structure with precise arrows and multiplicities.

Draw only what answers the question; keep diagrams as code.

Related: Modules 9, 24, 25.