UML Diagrams

By the Syncfusion DevTutorials team · Last updated

Learn to read and draw the two UML diagrams teams really use, class diagrams and sequence diagrams, then turn a QuickBite class diagram into working code.

Before a house is built, an architect creates a plan. Builders, plumbers, and electricians all use the same plan. This helps everyone understand where things should go without making assumptions.

Software teams work in a similar way. Before writing code, or while developing it, they often create diagrams to represent the system. The standard drawing language used for this is called UML (Unified Modeling Language).

In simple words: UML is a set of simple diagramming rules used to show classes and the relationships between them.

What You Will Learn

In this lesson, you will learn:

  1. How to read and create a class box.
  2. The five types of lines used to connect class boxes and what each one represents.
  3. How to read a sequence diagram.
  4. How to create diagrams using text with Mermaid.
  5. How to generate a diagram from code and how to write code based on a diagram.

You do not need to learn every part of UML. Most development teams mainly use two types of diagrams: the class diagram and the sequence diagram. This lesson focuses only on those two diagram types.

Why Draw at All?

Code can be long and detailed. A diagram gives a quick overview. One diagram can show many classes and their relationships, while the equivalent code may take several screens to read.

Diagrams are also easy to update. Moving a box in a diagram takes only a few seconds, while making the same structural change in code can take much longer.

Without diagrams, each team member may have a different understanding of how the system works. Those mental pictures are often not exactly the same.

The Class Box

A class diagram shows classes and the relationships between them. Each class is represented by a box with three sections.

The Three Parts

  • Top: the name of the class.
  • Middle: the fields, which store data.
  • Bottom: the methods, which define actions or behavior.

A minus sign (-) indicates a private member. A plus sign (+) indicates a public member. The type appears after a colon, such as balance: number.

From Box to Code

Here is a Rider class box. Each line of the box becomes a line of the TypeScript class.

The Rider class box: name, two private fields and two public methods

Step through reading the box and using the class it describes:

Here is the same idea in code:

From a class box to code

// The box says:  Rider | - name: string | - trips: number | + finishTrip(): void, + summary(): string
class Rider {
  private trips = 0;
  constructor(private readonly name: string) {}

  finishTrip(): void {
    this.trips += 1;
  }

  summary(): string {
    return `${this.name} has done ${this.trips} trips`;
  }
}

const ravi = new Rider("Ravi");
ravi.finishTrip();
ravi.finishTrip();
console.log(ravi.summary());

Expected output:

Ravi has done 2 trips

name and trips have a minus sign in the diagram, which means they are private in the code. finishTrip() and summary() have a plus sign, which means they are public methods.

The Five Lines

Class boxes alone do not tell the full story. The lines between them show how classes are connected. These five types of lines are enough to represent most relationships in UML.

Easy way to remember the lines: a triangle means "is a type of". A diamond means "owns". A normal arrow means "uses" or "knows about". A dashed line means the connection is temporary or less direct.

Composition: Owns It

Composition means one class creates and owns another class. If the owner object is removed, the owned object is also removed. In UML, this relationship is shown using a filled diamond on the owner's side.

A restaurant and its menu are a good example. Each restaurant has its own menu. The menu belongs to that restaurant and is not meaningful on its own.

Composition: the filled diamond sits next to the owner, Restaurant

Step through two restaurants being made:

Here is the same idea in code:

Composition: each restaurant makes and owns its own menu

class Menu {
  private readonly dishes: string[] = [];
  add(dish: string): void { this.dishes.push(dish); }
  get count(): number { return this.dishes.length; }
}

class Restaurant {
  readonly menu = new Menu(); // created here, owned here
  constructor(readonly name: string) {}
}

const a = new Restaurant("Saravana");
const b = new Restaurant("Paradise");
a.menu.add("Idli");
a.menu.add("Vada");

console.log(a.name, a.menu.count);
console.log(b.name, b.menu.count);

Expected output:

Saravana 2
Paradise 0

Saravana has 2 dishes, while Paradise has 0 dishes. Each time new Restaurant() is called, a new Menu object is created for that restaurant.

Association: Knows About It

Association means one class has a reference to another class, but it does not create or own it. In UML, this relationship is shown with a simple line and an open arrow.

An order knows which restaurant it belongs to. However, the order did not create the restaurant, and multiple orders can refer to the same restaurant.

Association: Order keeps a reference to a Restaurant it was given

Step through two orders made with the same restaurant:

Here is the same idea in code:

Association: an order is given a restaurant from outside

class Restaurant {
  constructor(readonly name: string) {}
}

class Order {
  // The order does not create the restaurant. It is handed one.
  constructor(private readonly restaurant: Restaurant, private readonly id: number) {}
  label(): string {
    return `Order ${this.id} from ${this.restaurant.name}`;
  }
}

const saravana = new Restaurant("Saravana");
const first = new Order(saravana, 1);
const second = new Order(saravana, 2);

console.log(first.label());
console.log(second.label());

Expected output:

Order 1 from Saravana
Order 2 from Saravana

Both orders refer to the same saravana object. The restaurant object is shared, and no copy of it is created.

Inheritance, Implements and Dependency

You learned about inheritance and interfaces in the OOP Basics lesson. In UML, both relationships use a hollow triangle arrow:

  • Inheritance (extends): shown with a solid line and a hollow triangle.
  • Implements (implements): shown with a dashed line and a hollow triangle.

Dependency is the weakest type of relationship. One class uses another class only for a short time, often as a method parameter. In UML, it is shown with a dashed arrow.

Here are all three in one small checkout diagram:

Card implements PaymentMethod (dashed, hollow triangle). Checkout only uses a Coupon as a parameter (dashed arrow).

Here is the same idea in code:

Inheritance, implements and dependency in code

interface PaymentMethod {          // drawn with <<interface>>
  pay(amount: number): string;
}

class Card implements PaymentMethod { // dashed line, hollow triangle
  pay(amount: number): string { return `card ${amount}`; }
}

class Coupon {
  constructor(readonly code: string, readonly off: number) {}
}

class Checkout {
  // Dependency: Coupon is only a parameter. Checkout does not keep it.
  priceWith(subtotal: number, coupon: Coupon): number {
    return subtotal - coupon.off;
  }
}

const total = new Checkout().priceWith(500, new Coupon("SAVE50", 50));
console.log(total);
console.log(new Card().pay(total));

Expected output:

450
card 450

In code, new Checkout().priceWith(500, new Coupon("SAVE50", 50)) gives 450, and new Card().pay(450) gives card 450. Checkout uses a Coupon only while the method is running and does not store it afterward. This is why the relationship is a dependency and not an association.

Multiplicity: How Many?

Small numbers written at the ends of relationship lines show how many objects can take part in that relationship. This is called multiplicity.

WrittenMeans
1exactly one
0..1zero or one
*any number, including zero
1..*one or more
0..2up to two

For example, "Menu 1 to * MenuItem" means that one menu can contain any number of menu items.

The Sequence Diagram

A class diagram shows what classes exist. A sequence diagram shows what happens and in what order.

You read a sequence diagram from top to bottom, like a comic strip. Each class has a vertical dotted line called a lifeline. Each arrow represents a call from one class to another. A dashed arrow represents a response coming back.

The Order Example

Here is the place-order flow written as a sequence diagram:

Placing an order: three calls and two answers, read from top to bottom

Now step through the same five arrows as the calls really happen in the code:

Here is the same idea in code:

A sequence diagram, written down as the calls happen

const steps: string[] = [];

class Notifier {
  send(text: string): void { steps.push(`OrderService -> Notifier: send(${text})`); }
}
class Payment {
  pay(amount: number): boolean {
    steps.push(`OrderService -> Payment: pay(${amount})`);
    steps.push(`Payment --> OrderService: ok`);
    return true;
  }
}
class OrderService {
  constructor(private readonly payment: Payment, private readonly notifier: Notifier) {}
  placeOrder(amount: number): number {
    steps.push(`Customer -> OrderService: placeOrder(${amount})`);
    this.payment.pay(amount);
    this.notifier.send("placed");
    steps.push(`OrderService --> Customer: order id 1`);
    return 1;
  }
}

new OrderService(new Payment(), new Notifier()).placeOrder(480);
for (const s of steps) console.log(s);

Expected output:

Customer -> OrderService: placeOrder(480)
OrderService -> Payment: pay(480)
Payment --> OrderService: ok
OrderService -> Notifier: send(placed)
OrderService --> Customer: order id 1

The calls happen in exactly the same order as the arrows in the sequence diagram, from top to bottom.

A sequence diagram represents one specific flow or scenario, not every possible scenario. Create a diagram for the main flow, such as "place an order". Avoid putting all error cases into the same diagram, because it quickly becomes difficult to read and understand.

Diagrams as Text

Drawing class boxes and relationships by hand can take time, and manually created diagrams can become outdated as the code changes. Because of this, many teams write diagrams as text using a simple language called Mermaid. A tool can then automatically generate the diagram from that text.

Here is a small QuickBite class diagram written as Mermaid text. Each line of text creates either a box or a relationship:

Line of textWhat it draws
classDiagramStarts a class diagram
class RestaurantA box named Restaurant (the same for Menu, MenuItem and Order)
Restaurant *-- Menu : ownsA filled diamond: Restaurant owns Menu
Menu --> MenuItem : holds manyAn arrow: Menu knows its items
Order --> Restaurant : usesAn arrow: Order knows its restaurant

A tool turns those eight lines into this picture:

The diagram drawn from the eight lines of Mermaid text

Change one line of text, and the picture changes with it.

The Class Diagram Lines in Mermaid

A class diagram starts with the keyword classDiagram. After that, each relationship is written on a separate line using a class name, a relationship symbol, and another class name.

You wantWriteRead it as
InheritanceDelivery <|-- ExpressDeliveryExpressDelivery is a type of Delivery
ImplementsPaymentMethod <|.. CardCard implements PaymentMethod
CompositionRestaurant *-- MenuRestaurant creates and owns Menu
AssociationOrder --> RestaurantOrder has a reference to Restaurant
DependencyCheckout ..> CouponCheckout uses a Coupon temporarily
An interface<<interface>> PaymentMethodPaymentMethod is an interface

The special shape (triangle or diamond) is always placed next to the parent, the interface, or the owner.

The Sequence Diagram Arrows in Mermaid

A sequence diagram starts with sequenceDiagram. Each arrow is written on its own line in the same order that the actions occur.

You wantWrite
A box for each classparticipant OrderService
A method callCustomer->>OrderService: placeOrder()
A response coming backOrderService-->>Customer: order id 1

Two dashes create a dashed arrow. Because of that, -->> represents a response, while ->> represents a method call.

Draw It Yourself: Diagram From Code

Now it is your turn to read the code and create its diagram. Type the Mermaid diagram text on the right side. The diagram updates automatically as you type. When you are done, click Check diagram.

The checker compares your classes and relationships with the code. If there is a mistake, it tells you which pair of classes to review and explains the reason.

How to Choose the Line

For every pair of classes, ask these four questions in this order:

  1. Does one class use extends or implements with another class? If yes, use a triangle relationship.
  2. Does one class create another class using new, keep it inside itself, and own it? If yes, use composition, *--.
  3. Does one class store an object that was passed to it in a field? If yes, use association, -->.
  4. Does one class use another class only as a method parameter? If yes, use dependency, ..>.

Fix a Wrong Diagram

Someone created this diagram too quickly, and it contains three mistakes. Compare the diagram with the code and correct those mistakes.

Draw a Sequence Diagram From Code

Follow the cancelOrder(42) method step by step. Each call made to another object becomes a solid arrow. Each value returned back becomes a dashed arrow.

Having trouble with a sequence diagram? Start at the first line of the method and read downward. Whenever you see this.something.method(), draw a solid arrow. Whenever a return statement sends a value back to the caller, draw a dashed arrow.

Lab: Code From Diagram

Now you will work in the opposite direction. A diagram is provided, and you need to write the code that matches it. Read each relationship carefully before you begin.

Predict the Output

Here is a composition example in C#. Read the code and try to predict what it prints before running it.

Common Mistakes

MistakeWrongCorrectWhy it happens
Using diamonds everywhereA diamond from Order to Restaurant.Use a simple arrow because the order only knows about the restaurant.The diamond looks important, so people often use it for every relationship.
Sharing an "owned" objectstatic menu shared by all restaurants.Each restaurant creates its own new Menu().A static field is quick to create and may appear to work in simple examples.
Wrong arrow directionThe triangle points to the child class.The triangle should point to the parent class.People often think of a family tree and naturally draw the arrow in the wrong direction.
Putting too much in one diagramShowing every class in the application in a single diagram.Create a separate diagram for each problem or question.It feels more complete, but it becomes difficult to read.
Diagram and code do not matchThe diagram still shows a class that no longer exists.Keep the diagram as text and update it along with the code.Images stored in documents or slides are easy to forget and become outdated.

Practice

What to Remember

  • A class box has three sections: class name, fields, and methods. - means private, and + means public.
  • A triangle means "is a type of". A diamond means "owns". An arrow means "knows about". A dashed line means a weaker relationship.
  • Multiplicity values show how many objects can take part in a relationship.
  • A sequence diagram is read from top to bottom, with each arrow representing a call.
  • Writing diagrams as text using Mermaid helps keep diagrams updated and synchronized with the code.

Further Reading

These are the sources used to check this lesson:

Next: Creational Patterns. You will learn better ways to create objects than writing new everywhere.