Design patterns are communicated through diagrams: the "Structure" section of any catalog is a class diagram, and the collaborations between objects are shown with sequence diagrams. If you cannot read these diagrams fluently, every pattern will cost you twice the effort. The good news: you do not need to master all of UML (which has 14 diagram types); for this course two are enough — class and sequence — plus a handful of relationships. In this lesson you will learn exactly that subset, see how we will draw it with mermaid throughout the rest of the course, and practice with the PideYa classes you already know. This is not a UML course: it is your survival kit for reading patterns.
Contents
- What UML is and which part we need
- The class diagram: classes, attributes and methods
- Interfaces and abstract classes
- The six relationships you must recognize
- The basic sequence diagram
- How we will draw all of this in the course: mermaid
What UML is and which part we need
UML (Unified Modeling Language) is the standard notation (since 1997) for drawing object-oriented systems. It is huge, but pattern catalogs use a tiny fraction of it:
| Diagram | What it shows | What patterns use it for |
|---|---|---|
| Class | Static structure: classes, interfaces and their relationships | The "Structure" section of each pattern |
| Sequence | Dynamic interaction: which messages objects send each other and in what order | The "Collaborations" section |
Everything else (use cases, activities, states, deployment...) stays outside this course. Golden rule when reading pattern diagrams: they are schematic, not construction blueprints. They show the essential participants and relationships, omitting getters, constructors and incidental details.
The class diagram: classes, attributes and methods
A class is drawn as a box with three compartments: name, attributes and methods.
classDiagram
class Order {
-Long id
-List~OrderLine~ lines
-OrderState state
+calculateTotal() double
+addLine(OrderLine line) void
+changeStatus(OrderState next) void
}
How to read each line:
- Visibility (the leading symbol):
+public,-private,#protected,~package-private. In pattern diagrams you will almost always see+on methods and-on attributes: standard encapsulation. - Attributes:
-Long idmeans "private attributeidof typeLong". In mermaid, generics are written with~:List~OrderLine~isList<OrderLine>. - Methods:
+calculateTotal() doubleis a public method with no parameters that returnsdouble. The return type goes at the end (in classic UML it is written: double; mermaid accepts it without the colon). - An underlined member is static; in mermaid it is marked with a trailing
$:+getInstance()$ Order. One in italics is abstract; in mermaid, with*:+cook()*.
In Java, that box corresponds to:
public class Order {
private Long id;
private List<OrderLine> lines;
private OrderState state;
public double calculateTotal() { /* ... */ return 0; }
public void addLine(OrderLine line) { /* ... */ }
public void changeStatus(OrderState next) { /* ... */ }
}Interfaces and abstract classes
Patterns live off abstractions ("program to an interface", as we saw in the previous lesson), so telling them apart in a diagram is vital:
- An interface is marked with the
<<interface>>stereotype above the name. - An abstract class is marked with
<<abstract>>(or the name in italics, in classic UML).
classDiagram
class Notifier {
<<interface>>
+notify(Customer customer, String message) void
}
class BasePaymentMethod {
<<abstract>>
-String merchantId
+process(double amount)* PaymentResult
+logAttempt() void
}
Practical difference when reading a pattern: an interface only declares a contract; an abstract class can also contribute common code to its children (like logAttempt() above) while leaving the variable part abstract (process, marked with *). Many patterns rely on exactly that combination.
The six relationships you must recognize
Here lies 80% of this lesson's value. The entire "grammar" of pattern diagrams is these six arrows:
| Relationship | Meaning | UML notation | In mermaid | PideYa example |
|---|---|---|---|---|
| Inheritance | "is a" (extends a class) | Arrow with hollow triangle toward the parent | `< | --` |
| Implementation | "fulfills the contract of" an interface | Hollow triangle with dashed line | `< | ..` |
| Association | "knows" (lasting reference) | Solid line (optionally with an arrow) | --> |
Order knows Customer |
| Aggregation | "has a" (weak whole-part: the parts outlive the whole) | Hollow diamond on the whole's side | o-- |
Courier aggregates Vehicle (the vehicle exists without it) |
| Composition | "is composed of" (strong whole-part: the parts die with the whole) | Filled diamond on the whole's side | *-- |
Order is composed of OrderLine |
| Dependency | "uses temporarily" (parameter, local variable, creation) | Dashed arrow | ..> |
InvoiceGenerator uses Order as a parameter |
And here they all are together on PideYa classes:
classDiagram
class Notifier {
<<interface>>
+notify(Customer c, String msg) void
}
class BasePaymentMethod {
<<abstract>>
+process(double amount)* PaymentResult
}
BasePaymentMethod <|-- CardPayment : inheritance
Notifier <|.. SmsNotifier : implementation
Order --> Customer : association
Courier o-- Vehicle : aggregation
Order *-- OrderLine : composition
InvoiceGenerator ..> Order : dependency
Keys to avoid mixing them up:
- The triangle always points to the abstraction (parent or interface). Solid line = class inheritance; dashed = interface implementation.
- The diamond goes on the "whole" side, not the part's. To distinguish aggregation from composition, ask yourself: if I delete the whole, does it make sense for the part to keep existing? If the order is canceled and deleted, its lines mean nothing on their own → composition (filled diamond). If the courier leaves the company, the motorbike still exists in the fleet → aggregation (hollow diamond).
- Association versus dependency: an association is an attribute (a reference that is kept); a dependency is one-off usage (a parameter or local variable). In Java:
private Customer customer;is association;generatePdf(Order o)is dependency. - Associations can carry a multiplicity:
"1" --> "0..*"reads "one order has zero to many lines". In mermaid:Order "1" *-- "1..*" OrderLine.
The Java correspondence of the three "having" relationships:
public class Order {
private Customer customer; // Association
private final List<OrderLine> lines = new ArrayList<>(); // Composition:
// the lines are created inside the order and nobody else references them
public void addLine(Dish dish, int quantity) {
lines.add(new OrderLine(dish, quantity)); // born and die with it
}
}
public class Courier {
private Vehicle vehicle; // Aggregation:
public void assignVehicle(Vehicle v) { this.vehicle = v; } // comes from outside
}An honest nuance: in Java the aggregation/composition difference is not enforced by the language (both are references); it is a design intention about lifecycle and ownership. In pattern diagrams it is rarely critical: when in doubt, read it as "has a".
The basic sequence diagram
The class diagram says who is who; the sequence diagram says what happens when the system runs: which objects take part, which messages (method calls) are sent, and in what temporal order (time flows downward).
PideYa scenario: a customer confirms their cart and the system charges and notifies.
sequenceDiagram
participant C as Customer
participant OS as OrderService
participant PM as PaymentMethod
participant N as Notifier
C->>OS: confirm(cart)
activate OS
OS->>OS: createOrder(cart)
OS->>PM: process(amount)
activate PM
PM-->>OS: PaymentResult
deactivate PM
OS->>N: notify(customer, "Order confirmed")
OS-->>C: orderConfirmed
deactivate OS
Elements you must recognize:
- Participants (at the top): the objects involved. From each one hangs its vertical lifeline.
- Synchronous message (
->>, solid arrow): a method call; the sender waits for the reply. - Reply (
-->>, dashed arrow): the returned value. - Activation bar (the rectangle on the lifeline,
activate/deactivate): the interval during which that object is executing something. - Self-message (
OS->>OS): the object calls one of its own methods.
In patterns, the sequence diagram answers the question the class diagram cannot: "fine, these are the classes... but who calls whom, and when?". You will see that in several patterns the class structure is almost identical and what distinguishes them is the sequence; that is why you should always read both.
How we will draw all of this in the course: mermaid
In this course every diagram is written in mermaid, a text notation that renders as a diagram. The advantage for you: you can copy any diagram from the course, paste it into mermaid.live and modify it to experiment. The complete cheat sheet of what we will use:
classDiagram %% starts a class diagram
class MyClass {
<<interface>> %% or <<abstract>>
-type privateAttribute
+method(Type param) ReturnType
+abstractMethod()* Type %% * = abstract
+staticMethod()$ Type %% $ = static
}
Parent <|-- Child %% inheritance
Interface <|.. Implementation %% implementation
A --> B : knows %% association (with optional label)
Whole o-- Part %% aggregation
Whole *-- Part %% composition
User ..> Used %% dependency
A "1" --> "0..*" B %% multiplicity
sequenceDiagram %% starts a sequence diagram
participant A as ReadableName
A->>B: call(args) %% synchronous message
B-->>A: reply %% return
activate B %% activation starts
deactivate B %% activation endsWith this cheat sheet you can read 100% of the diagrams in modules 2 through 6. When you see a pattern's structure in module 2, you will no longer be deciphering arrows: you will be reading design.
Common Mistakes and Tips
- Getting the inheritance triangle's direction wrong. The triangle touches the abstraction (parent/interface). In mermaid,
BasePaymentMethod <|-- CardPaymentreads "CardPayment inherits from BasePaymentMethod". If you read it backwards, you will understand every pattern backwards. - Getting the diamond's side wrong. The diamond sits against the whole (the container), not the part.
Order *-- OrderLine: the diamond is onOrder. - Expecting the diagram to say everything. A pattern diagram is a schematic: if the getters or the constructor do not appear, it is not that they do not exist; it is that they do not matter for understanding the pattern. Do not add them when drawing your own.
- Ignoring the difference between solid and dashed lines. Solid = strong, structural relationship (inheritance, association); dashed = weaker or contract-based relationship (implementation, dependency). This nuance changes the diagram's meaning.
- Reading only the class diagram and skipping the sequence diagram. Several patterns share an almost identical static structure; the difference lies in the dynamics. Get used to reading both right from the start.
- Tip: practice the reverse path. Take a small class from one of your projects and draw it in mermaid with its real relationships. Drawing is what makes the notation stick; reading alone does not.
Exercises
Exercise 1: from diagram to Java
Translate this diagram into Java class skeletons (without implementing the bodies):
classDiagram
class Promotion {
<<interface>>
+calculateDiscount(Order order) double
}
Promotion <|.. FirstOrderPromotion
DiscountCalculator ..> Promotion
DiscountCalculator ..> Order
Order "1" *-- "1..*" OrderLine
Exercise 2: from Java to diagram
Draw in mermaid (classDiagram) the classes and relationships of this code, choosing carefully among association, aggregation, composition and dependency:
public class Restaurant {
private final Menu menu = new Menu(); // created and owned by the restaurant
private List<Courier> favoriteCouriers; // assigned from the shared fleet
public Invoice invoiceMonth(InvoiceGenerator generator) {
return generator.generateMonthly(this);
}
}Exercise 3: reading a sequence
Look at the sequence diagram in section 5 and answer: (a) which object orchestrates the process?, (b) when is PaymentMethod active and what does it return?, (c) does the message to Notifier wait for a reply according to the diagram? Which message is a self-message?
Solutions
Solution 1:
public interface Promotion {
double calculateDiscount(Order order);
}
public class FirstOrderPromotion implements Promotion { // <|.. implementation
public double calculateDiscount(Order order) { return 0; }
}
public class DiscountCalculator {
// ..> dependency: uses Promotion and Order as parameters, not as attributes
public double calculate(Order order, Promotion promotion) { return 0; }
}
public class Order {
// *-- composition 1 to 1..*: the order owns its lines (at least one)
private final List<OrderLine> lines = new ArrayList<>();
}
public class OrderLine { }Solution 2:
classDiagram
Restaurant "1" *-- "1" Menu : composition
Restaurant o-- Courier : aggregation
Restaurant ..> InvoiceGenerator : dependency
Restaurant ..> Invoice : dependency
Reasoning: the Menu is created and owned by the restaurant (it dies with it) → composition. The couriers exist outside the restaurant and are only associated with it as favorites → aggregation (a plain association --> would also be defensible; the important thing is to rule out composition). InvoiceGenerator and Invoice only appear as a parameter and a return value of a method → dependencies.
Solution 3: (a) OrderService: it receives the customer's request and calls everyone else. (b) PaymentMethod is active only between the process(amount) call and its reply; it returns a PaymentResult. (c) As drawn, notify(...) has no return arrow: the diagram shows no reply (it reads as a call whose result does not matter in this scenario). The self-message is OS->>OS: createOrder(cart): OrderService invoking one of its own methods.
Conclusion
You now have the diagram-reading kit you will use throughout the course: the class box with visibilities, the <<interface>> and <<abstract>> stereotypes, the six relationships (inheritance, implementation, association, aggregation, composition and dependency, with their arrows and diamonds correctly oriented) and the sequence diagram for the dynamics. You also know the exact mermaid syntax in which all the course's diagrams are written, ready to copy and experiment with.
With the reading tools in hand, it is time to unfold the map: how many patterns are there, how are they organized, and what does each family do? That map — which is also the itinerary of modules 2, 3 and 4 — is the next lesson: Classification of Design Patterns.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
