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 : ownsComplete 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" ProductInterview 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.