Object-oriented design with UML
Reading and writing class diagrams: visibility markers, relationship arrows, multiplicity, the aggregation vs composition difference, and going from a requirements text to classes.
UML (Unified Modeling Language) is the standard language for drawing software before writing code. In this course you learn the most used part of UML, the class diagram. In the exam it comes in two directions: “translate this diagram into Java” and “derive a class diagram from this requirement”. If you do not mix up the arrows and the markers, it is easy points.
Golden rules:
- Visibility markers:
+public,-private,#protected,~package. - In UML the type is written at the end:
- name : String→private String name;. - A hollow triangle is “is-a”: a solid line is
extends, a dashed line isimplements. - A diamond is “has-a”: a hollow diamond is aggregation, a filled diamond is composition. The diamond is on the whole side.
- The “many” side of a multiplicity becomes a collection field.
The class box
A class is a rectangle with three compartments: the name at the top, the fields (attributes) in the middle, the operations at the bottom.
+----------------------------------------------+
| Account | name (italic if abstract)
+----------------------------------------------+
| - owner : String | fields
| - balance : double = 0.0 |
| - count : int (underlined) | static
+----------------------------------------------+
| + Account(owner : String) | constructor
| + deposit(amount : double) : void | operations
| + getBalance() : double |
| + getCount() : int (underlined) | static
| # audit() : boolean | protected
+----------------------------------------------+
| UML | Java |
|---|---|
- balance : double = 0.0 | private double balance = 0.0; |
+ deposit(amount : double) : void | public void deposit(double amount) |
# audit() : boolean | protected boolean audit() |
~ setAvailable(a : boolean) | void setAvailable(boolean a) (no modifier) |
| Underlined member | static |
Italic name or {abstract} | abstract class / abstract method |
<<interface>> | interface |
{readOnly} | final field |
- items : Item [0..*] | private List<Item> items; or an array |
PITFALL · where the type goes. In UML it is
getBalance() : double, in Java it isdouble getBalance(). In a translation question, whoever forgets to swap the types writes a broken signature.
Relationships
The lines between classes and the shapes at their ends tell the type of the relationship.
| Relationship | Drawing | Meaning | Java equivalent |
|---|---|---|---|
| Generalization (inheritance) | Solid line, hollow triangle at the superclass | is-a | class Dog extends Animal |
| Realization | Dashed line, hollow triangle at the interface | can-do | class Invoice implements Payable |
| Association | Solid line (direction if there is an arrow) | knows / keeps | Field: private Member member; |
| Aggregation | Solid line, hollow diamond at the whole | has-a, part is independent | An object that comes from outside is kept in a list |
| Composition | Solid line, filled diamond at the whole | has-a, part belongs to the whole | The whole creates the part itself |
| Dependency | Dashed arrow | uses temporarily | Parameter or local variable |
Person (italic: abstract) <<interface>>
^ Payable
| solid line + hollow triangle ^
| : dashed + hollow triangle
Staff ...................................:
Library ◆---------- 0..* Loan (composition)
Team ◇---------- 0..* Player (aggregation)
Member 1 ---------- 0..3 Loan (association + multiplicity)
Order - - - - - - - -> Printer (dependency)
interface Payable { double amount(); }
abstract class Person {
protected String name;
Person(String n) { name = n; }
abstract String role();
}
class Staff extends Person implements Payable {
private final double salary;
Staff(String n, double s) { super(n); salary = s; }
String role() { return "staff"; }
public double amount() { return salary; }
}
Person s = new Staff("Ayse", 4200);
System.out.println(s.role() + " " + ((Payable) s).amount() + " " + (s instanceof Payable));
staff 4200.0 true
Aggregation and composition
Both are “part-whole” (has-a) relationships. The difference is the life of the part:
- Composition (filled diamond): the part is born and dies together with the whole, and is not shared with another whole. A house and its rooms, a car and its engine. In code, the whole usually creates the part with
newin its own constructor and does not hand it out. - Aggregation (hollow diamond): the part lives independently and can belong to more than one whole. A team and its players, a course and its students. In code the part comes from outside (
add(Player p)).
class Engine { final int hp; Engine(int hp) { this.hp = hp; } }
class Car {
private final Engine engine; // composition
Car(int hp) { engine = new Engine(hp); }
int power() { return engine.hp; }
}
class Player { final String name; Player(String n) { name = n; } }
class Team {
private final List<Player> players = new ArrayList<>(); // aggregation
void add(Player p) { players.add(p); }
int size() { return players.size(); }
}
Car car = new Car(120);
System.out.println(car.power());
Player p = new Player("Arda");
Team t1 = new Team(), t2 = new Team();
t1.add(p); t2.add(p); // the same player on two teams
t1 = null; // the team is gone, the player remains
System.out.println(t2.size() + " " + p.name);
120
1 Arda
TIP · if you are not sure, association. Instructors often do not ask for the exact difference between aggregation and a plain association. They do want composition, though: if you answer “no” to the question “can the part exist without the whole?”, use the filled diamond.
Multiplicity
The numbers written at the ends of the line say how many objects on this side a single object on the opposite side is related to.
| Notation | Meaning | In code |
|---|---|---|
1 | exactly one | Reference field, must not be null |
0..1 | none or one | Reference field, can be null |
0..* or * | zero or many | List<T> field |
1..* | at least one | List<T>, not left empty |
0..3 | at most three | List<T> + size() < 3 check |
Member 1 ---- 0..3 Loan is read like this: a Member has between 0 and 3 Loans; each Loan has exactly 1 Member.
From requirements to classes: a library example
The requirements document:
The campus library has books and members. Each book has an ISBN and a title. A member can borrow at most three books at a time. A book that is on loan cannot be given to anyone else until it is returned. The library records, for each loan, which member it was made to, for which book, and for how many days.
Step 1: extract the nouns (noun extraction). library, book, member, ISBN, title, loan, day. Those that have their own data and behavior are classes: Library, Book, Member, Loan. Those that are a property of something are fields: isbn, title, days.
Step 2: extract the verbs. to borrow, to return, to record → lend, giveBack. The rules “cannot be given” and “at most three” become checks inside these methods.
Step 3: draw the relationships and multiplicity.
Library ◇------------ 0..* Book
Library ◆------------ 0..* Loan
Member 1 ----------- 0..3 Loan
Loan 0..1 -------- 1 Book (a book has at most 1 active loan at a time)
Step 4: translate to code.
class Loan {
private final Book book;
private final Member member;
private final int days;
Loan(Book b, Member m, int days) { book = b; member = m; this.days = days; }
Book getBook() { return book; }
Member getMember() { return member; }
public String toString() { return member.getName() + " -> " + book + " for " + days + " days"; }
}
class Library {
private final List<Book> books = new ArrayList<>();
private final List<Loan> loans = new ArrayList<>();
void addBook(Book b) { books.add(b); }
Loan lend(Book b, Member m, int days) {
if (!b.isAvailable()) { System.out.println("Not available: " + b); return null; }
if (!m.canBorrow()) { System.out.println("Limit reached: " + m.getName()); return null; }
Loan l = new Loan(b, m, days); // Library creates the Loan: composition
b.setAvailable(false);
m.addLoan(l);
loans.add(l);
return l;
}
void giveBack(Loan l) {
l.getBook().setAvailable(true);
l.getMember().removeLoan(l);
loans.remove(l);
}
int activeLoans() { return loans.size(); }
}
Member has List<Loan> loans and canBorrow() → loans.size() < 3. The scenario:
Loan l1 = lib.lend(b1, ece, 14);
System.out.println(l1);
lib.lend(b1, mert, 7); // b1 is on loan
lib.lend(b2, ece, 7);
lib.lend(b3, ece, 7);
lib.lend(b4, ece, 7); // Ece has 3 loans
System.out.println(ece.loanCount() + " " + lib.activeLoans());
lib.giveBack(l1);
System.out.println(b1 + " | " + ece.loanCount() + " " + lib.activeLoans());
System.out.println(Book.getCreated());
Ece -> Clean Code (on loan) for 14 days
Not available: Clean Code (on loan)
Limit reached: Ece
3 3
Clean Code | 2 2
4
Note the design decisions: the information “is the book on loan” is kept by Book (available), the information “how many books does the member have” is kept by Member, and Library combines the rules. Each class is responsible for its own data; this is the design-level counterpart of encapsulation.
Common mistakes
| Wrong idea | The truth |
|---|---|
- x : int is a public field | - is private; public is + |
| The type in UML is at the end in Java too | In Java it is at the start: int x, double getX() |
| The inheritance triangle points to the subclass | The triangle points to the super class / interface |
| A dashed triangle is also extends | A dashed triangle is implements |
| The diamond is on the part side | The diamond is on the whole side |
| Aggregation and composition are the same | In composition the part cannot live without the whole |
0..* is a single reference field | A collection (List) field |
| Every noun in the requirement is a class | A noun that is a property of something becomes a field; “system” is not a class |
| An underlined member is a pointless decoration | It means static |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
In a UML class box, what does - balance : double mean?
Show answer
The second compartment of a class box holds the fields. - is private, then the name, then the type after the colon. In Java: private double balance;. In UML the type is written at the end, in Java at the start.
What does an underlined operation + getCount() : int in a class box mean?
Show answer
+ is public. An underlined member means static (belongs to the class). Inside the parentheses are the parameters, and after the colon is the return type. For methods that return void, : void is written or nothing is written.
Match each UML relationship line to Java: (1) solid line, hollow triangle head; (2) dashed line, hollow triangle head; (3) solid line, filled diamond at the whole.
Show answer
A hollow triangle is always "is-a": a solid line is class inheritance (generalization, extends), a dashed line is interface realization (realization, implements). The triangle points to the superclass / interface. A filled diamond is composition: the diamond sits on the whole side, and the life of the part is tied to the whole.
Which one is composition and which one is aggregation?
class Car {
private final Engine engine;
Car(int hp) { engine = new Engine(hp); }
}
class Team {
private final List<Player> players = new ArrayList<>();
void add(Player p) { players.add(p); }
}Show answer
Car creates its engine itself and does not hand it out: the life of the engine is tied to the car's, filled diamond (composition). Team receives the players from outside; the same player can be on two teams and the player lives on even if the team is deleted (in the test run, after t1 = null, t2 still held 1 player): hollow diamond (aggregation).
A UML association reads Member 1 ---- 0..3 Loan. What does it mean, and how is it usually coded in Member?
Show answer
Multiplicity is read at the opposite end: 0..3 Loans are linked to one Member; each Loan has exactly 1 Member. The "many" side becomes a collection field (List<Loan> loans), and the upper limit (3) is checked in code: loans.size() < 3. The single side (1) is a plain reference field in Loan: private Member member;.
Requirement: "A student enrolls in courses. Each course has a code and a capacity. The system rejects an enrollment when the course is full." Which words become classes, attributes and operations?
Show answer
Noun extraction: nouns that have their own data and behavior become classes (Student, Course), nouns that are a property of something else become fields (code, capacity). Verbs become operations (enroll). "System" is usually not made a class; it is the whole program itself. "full" is not a separate field, it is computed with enrolled == capacity.
What is printed?
// Library.lend(b, m, days): prints "Not available: <book>" if the book is on loan,
// "Limit reached: <name>" if the member already has 3 loans, otherwise creates a Loan.
// Book.toString(): title + (available ? "" : " (on loan)")
Loan l1 = lib.lend(b1, ece, 14);
System.out.println(l1);
lib.lend(b1, mert, 7);
lib.lend(b2, ece, 7);
lib.lend(b3, ece, 7);
lib.lend(b4, ece, 7);
System.out.println(ece.loanCount() + " " + lib.activeLoans());
lib.giveBack(l1);
System.out.println(b1 + " | " + ece.loanCount() + " " + lib.activeLoans());Show answer
When l1 is printed, the book is already on loan, so toString adds "(on loan)". Mert cannot take the same book. Ece takes b2 and b3 (3 loans), and at b4 the limit is full. The counts are 3 3. After the return the book is available again (no suffix), and Ece has 2 and the library has 2 active loans.
Which UML element shows that Order only uses Printer temporarily, as a method parameter, without keeping a field?
- Association (solid line)
- Dependency (dashed arrow)
- Composition (filled diamond)
- Generalization (hollow triangle)
Show answer
A dependency means "uses but does not keep": a parameter, a local variable or a static call. It is drawn with a dashed arrow with an open head. A reference kept as a field is an association (solid line).
Write the Java class for this UML box.
+-------------------------------------------+
| Book |
+-------------------------------------------+
| - isbn : String {readOnly} |
| - title : String |
| - available : boolean = true |
| - created : int (underlined) |
+-------------------------------------------+
| + Book(isbn : String, title : String) |
| + getIsbn() : String |
| + isAvailable() : boolean |
| ~ setAvailable(a : boolean) : void |
| + getCreated() : int (underlined) |
+-------------------------------------------+Show answer
Translate it line by line. {readOnly} is a final field, = true is the initial value, underlined members are static, ~ is package access (no modifier is written). Let the constructor increment the counter for every object:
public class Book {
private final String isbn;
private String title;
private boolean available = true;
private static int created = 0; public Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
created++;
}
public String getIsbn() { return isbn; }
public boolean isAvailable() { return available; }
void setAvailable(boolean a) { available = a; }
public static int getCreated() { return created; }
}After four books are created, Book.getCreated() returns 4.In a class diagram Staff has a solid line with a hollow triangle to abstract class *Person* (italic) and a dashed line with a hollow triangle to <<interface>> Payable. Write the class header, and say what s instanceof Payable prints for Person s = new Staff("Ayse", 4200);.
Show answer
Solid line + triangle → extends, dashed + triangle → implements. An italic name means an abstract class, and Staff must write its abstract methods. Even though the variable has type Person, the object is a Staff, and Staff is a Payable: true. In the test run staff 4200.0 true was printed.
Write class Course from the diagram Course (- code : String, - capacity : int) 1 ---- 0..* Student, with operations + enroll(s : Student) : boolean (false if the course is full or the student is already enrolled) and + getEnrolledCount() : int.
Show answer
The 0..* side becomes a collection. The business rules (is it full, is the student already enrolled) are in enroll:
import java.util.ArrayList;
import java.util.List;public class Course {
private final String code;
private final int capacity;
private final List<Student> students = new ArrayList<>(); public Course(String code, int capacity) {
this.code = code;
this.capacity = capacity;
}
public boolean enroll(Student s) {
if (students.size() >= capacity || students.contains(s)) return false;
students.add(s);
return true;
}
public int getEnrolledCount() { return students.size(); }
public String getCode() { return code; }
}With capacity 2, enrolling a, a, b, d in that order gives true false true false, and the count is 2.Verification: All the Java code translated from UML was run with JDK 26 (java verify/SEN1002/W07.java); the outputs were taken from there.