SEN1002 · Week 7 · Case Study: Object Oriented Design with the UML

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.

UMLclass diagramvisibilityassociationmultiplicityaggregationcompositiongeneralizationrealizationdependencyrequirements documentnoun extraction

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:

  1. Visibility markers: + public, - private, # protected, ~ package.
  2. In UML the type is written at the end: - name : String → private String name;.
  3. A hollow triangle is “is-a”: a solid line is extends, a dashed line is implements.
  4. A diamond is “has-a”: a hollow diamond is aggregation, a filled diamond is composition. The diamond is on the whole side.
  5. 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
+----------------------------------------------+
UMLJava
- balance : double = 0.0private double balance = 0.0;
+ deposit(amount : double) : voidpublic void deposit(double amount)
# audit() : booleanprotected boolean audit()
~ setAvailable(a : boolean)void setAvailable(boolean a) (no modifier)
Underlined memberstatic
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 is double 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.

RelationshipDrawingMeaningJava equivalent
Generalization (inheritance)Solid line, hollow triangle at the superclassis-aclass Dog extends Animal
RealizationDashed line, hollow triangle at the interfacecan-doclass Invoice implements Payable
AssociationSolid line (direction if there is an arrow)knows / keepsField: private Member member;
AggregationSolid line, hollow diamond at the wholehas-a, part is independentAn object that comes from outside is kept in a list
CompositionSolid line, filled diamond at the wholehas-a, part belongs to the wholeThe whole creates the part itself
DependencyDashed arrowuses temporarilyParameter 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 new in 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.

NotationMeaningIn code
1exactly oneReference field, must not be null
0..1none or oneReference field, can be null
0..* or *zero or manyList<T> field
1..*at least oneList<T>, not left empty
0..3at most threeList<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 ideaThe truth
- x : int is a public field- is private; public is +
The type in UML is at the end in Java tooIn Java it is at the start: int x, double getX()
The inheritance triangle points to the subclassThe triangle points to the super class / interface
A dashed triangle is also extendsA dashed triangle is implements
The diamond is on the part sideThe diamond is on the whole side
Aggregation and composition are the sameIn composition the part cannot live without the whole
0..* is a single reference fieldA collection (List) field
Every noun in the requirement is a classA noun that is a property of something becomes a field; “system” is not a class
An underlined member is a pointless decorationIt means static

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

In a UML class box, what does - balance : double mean?

Show answer
a private double field balance

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.

Question 2easy

What does an underlined operation + getCount() : int in a class box mean?

Show answer
public static int getCount()

+ 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.

Question 3medium

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
(1) extends, (2) implements, (3) composition

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.

Question 4medium

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-Engine composition, Team-Player aggregation

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).

Question 5medium

A UML association reads Member 1 ---- 0..3 Loan. What does it mean, and how is it usually coded in Member?

Show answer
Each member has between 0 and 3 loans; a List<Loan> field in Member + a limit check

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;.

Question 6medium

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
Classes: Student, Course. Attributes: code, capacity. Operation: enroll

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.

Question 7hard

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
Ece -> Clean Code (on loan) for 14 days / Not available: Clean Code (on loan) / Limit reached: Ece / 3 3 / Clean Code | 2 2

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.

Question 8medium

Which UML element shows that Order only uses Printer temporarily, as a method parameter, without keeping a field?

  1. Association (solid line)
  2. Dependency (dashed arrow)
  3. Composition (filled diamond)
  4. Generalization (hollow triangle)
Show answer
Dependency (dashed arrow)

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).

Question 9medium

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
The class below (readOnly → final, underlined → static, ~ → no modifier)

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.

Question 10easy

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
class Staff extends Person implements Payable; true

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.

Question 11hard

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
a List<Student> field + enroll that checks capacity and duplicates

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.

Review card

Class box has 3 compartments: name / fields / operations. Italic name or {abstract} = abstract. + public - private # protected ~ package. Underlined = static. Field: - name : Type [= initial value] Operation: + name(p : Type) : ReturnType <<interface>> is written on top. {readOnly} → final. Solid line + hollow triangle → extends (generalization). The triangle points to the SUPER class. Dashed + hollow triangle → implements (realization). Solid line → association (kept as a field). If there is an arrow, it gives direction: who knows whom. Dashed arrow → dependency (only uses: parameter / local). Hollow diamond ◇ aggregation: the part lives independently (Team-Player). The diamond is on the WHOLE side. Filled diamond ◆ composition: the part is born and dies with the whole (Car-Engine, House-Room). Multiplicity: 1, 0..1, 0..*, 1..*, * . Read at the opposite end. The "many" side → List field. Class from requirements: nouns → class/field, verbs → method. "System" is not a class.