Mermaid Class Diagrams (UML)

UML class diagrams show the structure of a system by displaying classes, their attributes, methods, and relationships. Essential for documenting object-oriented designs and system architecture.

Use Case

Use class diagrams when you need to:

  • Document system architecture
  • Show class relationships and hierarchies
  • Design object-oriented systems
  • Communicate structure to team members

UML Symbol Reference

Class Structure

1β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
2β”‚   ClassName     β”‚  ← Class name
3β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
4β”‚ - privateAttr   β”‚  ← Attributes (- private, + public, # protected, ~ package)
5β”‚ + publicAttr    β”‚
6β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
7β”‚ + method()      β”‚  ← Methods
8β”‚ - helper()      β”‚
9β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Relationships

SymbolMeaningDescription
<|--Inheritance"extends" or "is-a" relationship
*--CompositionStrong ownership, part cannot exist without whole
o--AggregationWeak ownership, part can exist independently
-->AssociationGeneral relationship
..|>RealizationImplements interface
..>DependencyUses or depends on

Multiplicity

SymbolMeaning
"1"Exactly one
"0..1"Zero or one
"1..*"One or more
"*"Zero or more
"n"Exactly n

Code

 1```mermaid
 2classDiagram
 3    class Animal {
 4        +String name
 5        +int age
 6        +makeSound()
 7    }
 8    
 9    class Dog {
10        +String breed
11        +bark()
12    }
13    
14    Animal <|-- Dog
15```

Result:

Explanation

  • class ClassName - Define a class
  • + - Public visibility
  • - - Private visibility
  • # - Protected visibility
  • ~ - Package/Internal visibility
  • <|-- - Inheritance arrow
  • *-- - Composition
  • o-- - Aggregation

Examples

Example 1: Basic Class Hierarchy

 1```mermaid
 2classDiagram
 3    class Shape {
 4        <<abstract>>
 5        -String color
 6        -int x
 7        -int y
 8        +draw()*
 9        +move(int, int)
10        +getArea()* double
11    }
12    
13    class Circle {
14        -double radius
15        +draw()
16        +getArea() double
17    }
18    
19    class Rectangle {
20        -double width
21        -double height
22        +draw()
23        +getArea() double
24    }
25    
26    Shape <|-- Circle
27    Shape <|-- Rectangle
28```

Result:

Example 2: Relationships and Multiplicity

 1```mermaid
 2classDiagram
 3    class University {
 4        +String name
 5        +addDepartment()
 6    }
 7    
 8    class Department {
 9        +String name
10        +addProfessor()
11    }
12    
13    class Professor {
14        +String name
15        +teach()
16    }
17    
18    class Student {
19        +String name
20        +enroll()
21    }
22    
23    class Course {
24        +String title
25        +int credits
26    }
27    
28    University "1" *-- "1..*" Department : contains
29    Department "1" o-- "*" Professor : employs
30    Professor "1" --> "*" Course : teaches
31    Student "*" --> "*" Course : enrolls in
32```

Result:

Example 3: Interfaces and Implementation

 1```mermaid
 2classDiagram
 3    class Drawable {
 4        <<interface>>
 5        +draw()
 6    }
 7    
 8    class Serializable {
 9        <<interface>>
10        +serialize() String
11        +deserialize(String)
12    }
13    
14    class Document {
15        -String content
16        -String author
17        +save()
18        +load()
19    }
20    
21    Drawable <|.. Document : implements
22    Serializable <|.. Document : implements
23```

Result:

Example 4: Stereotypes and Annotations

 1```mermaid
 2classDiagram
 3    class DataProcessor {
 4        <<service>>
 5        +process(data)
 6    }
 7    
 8    class DataRepository {
 9        <<repository>>
10        +save(entity)
11        +findById(id)
12    }
13    
14    class DataEntity {
15        <<entity>>
16        +Long id
17        +String data
18    }
19    
20    DataProcessor ..> DataRepository : uses
21    DataRepository --> DataEntity : manages
22```

Result:

Notes

  • Use <<stereotype>> for annotations (abstract, interface, service, etc.)
  • * after method name indicates abstract method
  • Return types can be specified after method: getArea() double
  • Use meaningful relationship labels for clarity

Gotchas/Warnings

  • ⚠️ Complexity: Large diagrams become hard to read - break into multiple diagrams
  • ⚠️ Layout: Mermaid auto-layouts - you can't control exact positioning
  • ⚠️ Generics: Generic types like List<T> need special handling
  • ⚠️ Bidirectional: Show only one direction for bidirectional relationships

Practical Example: Python E-Commerce System

Python Code

  1from abc import ABC, abstractmethod
  2from typing import List, Optional
  3from datetime import datetime
  4
  5# Abstract base class (inheritance)
  6class PaymentProcessor(ABC):
  7    """Abstract payment processor - demonstrates inheritance"""
  8    
  9    @abstractmethod
 10    def process_payment(self, amount: float) -> bool:
 11        pass
 12    
 13    @abstractmethod
 14    def refund(self, transaction_id: str) -> bool:
 15        pass
 16
 17# Concrete implementations
 18class StripeProcessor(PaymentProcessor):
 19    def __init__(self, api_key: str):
 20        self.api_key = api_key
 21    
 22    def process_payment(self, amount: float) -> bool:
 23        print(f"Processing ${amount} via Stripe")
 24        return True
 25    
 26    def refund(self, transaction_id: str) -> bool:
 27        print(f"Refunding transaction {transaction_id}")
 28        return True
 29
 30class PayPalProcessor(PaymentProcessor):
 31    def __init__(self, client_id: str, secret: str):
 32        self.client_id = client_id
 33        self.secret = secret
 34    
 35    def process_payment(self, amount: float) -> bool:
 36        print(f"Processing ${amount} via PayPal")
 37        return True
 38    
 39    def refund(self, transaction_id: str) -> bool:
 40        print(f"Refunding transaction {transaction_id}")
 41        return True
 42
 43# Value object (composition)
 44class Address:
 45    """Address value object - demonstrates composition"""
 46    
 47    def __init__(self, street: str, city: str, country: str, postal_code: str):
 48        self.street = street
 49        self.city = city
 50        self.country = country
 51        self.postal_code = postal_code
 52    
 53    def format(self) -> str:
 54        return f"{self.street}, {self.city}, {self.country} {self.postal_code}"
 55
 56# Entity with composition
 57class Customer:
 58    """Customer entity - demonstrates composition"""
 59    
 60    def __init__(self, customer_id: str, name: str, email: str, address: Address):
 61        self.customer_id = customer_id
 62        self.name = name
 63        self.email = email
 64        self.address = address  # Composition: Customer HAS-A Address
 65        self.orders: List['Order'] = []
 66    
 67    def add_order(self, order: 'Order'):
 68        self.orders.append(order)
 69
 70# Product entity
 71class Product:
 72    """Product entity"""
 73    
 74    def __init__(self, product_id: str, name: str, price: float, stock: int):
 75        self.product_id = product_id
 76        self.name = name
 77        self.price = price
 78        self.stock = stock
 79    
 80    def reduce_stock(self, quantity: int) -> bool:
 81        if self.stock >= quantity:
 82            self.stock -= quantity
 83            return True
 84        return False
 85
 86# Order item (composition)
 87class OrderItem:
 88    """Order item - demonstrates composition"""
 89    
 90    def __init__(self, product: Product, quantity: int):
 91        self.product = product  # Composition
 92        self.quantity = quantity
 93        self.price_at_purchase = product.price
 94    
 95    def get_total(self) -> float:
 96        return self.price_at_purchase * self.quantity
 97
 98# Main aggregate (dependency injection + composition)
 99class Order:
100    """Order aggregate - demonstrates DI and composition"""
101    
102    def __init__(self, order_id: str, customer: Customer, 
103                 payment_processor: PaymentProcessor):  # Dependency Injection
104        self.order_id = order_id
105        self.customer = customer  # Composition
106        self.items: List[OrderItem] = []  # Composition
107        self.payment_processor = payment_processor  # DI (Dependency Inversion)
108        self.status = "pending"
109        self.created_at = datetime.now()
110    
111    def add_item(self, product: Product, quantity: int):
112        """Depends on Product (dependency)"""
113        if product.reduce_stock(quantity):
114            item = OrderItem(product, quantity)
115            self.items.append(item)
116    
117    def calculate_total(self) -> float:
118        return sum(item.get_total() for item in self.items)
119    
120    def checkout(self) -> bool:
121        """Uses injected payment processor (Dependency Inversion)"""
122        total = self.calculate_total()
123        if self.payment_processor.process_payment(total):
124            self.status = "paid"
125            return True
126        return False
127
128# Repository (depends on Order)
129class OrderRepository:
130    """Repository - demonstrates dependency"""
131    
132    def __init__(self):
133        self.orders: List[Order] = []
134    
135    def save(self, order: Order):  # Depends on Order
136        self.orders.append(order)
137    
138    def find_by_id(self, order_id: str) -> Optional[Order]:
139        return next((o for o in self.orders if o.order_id == order_id), None)
140
141# Usage example
142if __name__ == "__main__":
143    # Create payment processor (can be swapped - Dependency Inversion)
144    payment = StripeProcessor(api_key="sk_test_123")
145    
146    # Create customer with address (Composition)
147    address = Address("123 Main St", "New York", "USA", "10001")
148    customer = Customer("C001", "John Doe", "john@example.com", address)
149    
150    # Create products
151    laptop = Product("P001", "Laptop", 999.99, 10)
152    mouse = Product("P002", "Mouse", 29.99, 50)
153    
154    # Create order with DI
155    order = Order("O001", customer, payment)
156    order.add_item(laptop, 1)
157    order.add_item(mouse, 2)
158    
159    # Process order
160    order.checkout()
161    
162    # Save to repository
163    repo = OrderRepository()
164    repo.save(order)

UML Diagram

 1```mermaid
 2classDiagram
 3    %% Abstract base class
 4    class PaymentProcessor {
 5        <<abstract>>
 6        +process_payment(amount) bool*
 7        +refund(transaction_id) bool*
 8    }
 9    
10    %% Concrete implementations (Inheritance)
11    class StripeProcessor {
12        -String api_key
13        +process_payment(amount) bool
14        +refund(transaction_id) bool
15    }
16    
17    class PayPalProcessor {
18        -String client_id
19        -String secret
20        +process_payment(amount) bool
21        +refund(transaction_id) bool
22    }
23    
24    %% Value object
25    class Address {
26        +String street
27        +String city
28        +String country
29        +String postal_code
30        +format() String
31    }
32    
33    %% Entities
34    class Customer {
35        +String customer_id
36        +String name
37        +String email
38        +List~Order~ orders
39        +add_order(order)
40    }
41    
42    class Product {
43        +String product_id
44        +String name
45        +float price
46        +int stock
47        +reduce_stock(quantity) bool
48    }
49    
50    class OrderItem {
51        +int quantity
52        +float price_at_purchase
53        +get_total() float
54    }
55    
56    class Order {
57        +String order_id
58        +String status
59        +DateTime created_at
60        +List~OrderItem~ items
61        +add_item(product, quantity)
62        +calculate_total() float
63        +checkout() bool
64    }
65    
66    class OrderRepository {
67        -List~Order~ orders
68        +save(order)
69        +find_by_id(order_id) Order
70    }
71    
72    %% Inheritance relationships
73    PaymentProcessor <|-- StripeProcessor : extends
74    PaymentProcessor <|-- PayPalProcessor : extends
75    
76    %% Composition relationships (strong ownership)
77    Customer *-- Address : has
78    Order *-- OrderItem : contains
79    OrderItem *-- Product : references
80    
81    %% Aggregation (weak ownership)
82    Customer o-- Order : places
83    
84    %% Dependency Injection (uses interface)
85    Order --> PaymentProcessor : uses (DI)
86    
87    %% Dependencies
88    OrderRepository ..> Order : depends on
89    Order ..> Product : depends on
90```

Result:

Relationship Explanations

  1. Inheritance (<|--): StripeProcessor and PayPalProcessor extend PaymentProcessor

    • IS-A relationship
    • Subclasses inherit abstract methods
  2. Composition (*--): Strong ownership, lifecycle dependent

    • Customer has Address - address can't exist without customer
    • Order contains OrderItem - items can't exist without order
    • OrderItem references Product - captures product state
  3. Aggregation (o--): Weak ownership, independent lifecycle

    • Customer places Order - orders can exist independently
  4. Dependency Injection (-->): Order uses PaymentProcessor interface

    • Dependency Inversion Principle
    • Order depends on abstraction, not concrete implementation
    • Payment processor can be swapped at runtime
  5. Dependency (..>): Uses but doesn't own

    • OrderRepository depends on Order - needs it to function
    • Order depends on Product - uses it but doesn't own it

This example demonstrates SOLID principles:

  • Single Responsibility: Each class has one job
  • Open/Closed: Can add new payment processors without modifying Order
  • Liskov Substitution: Any PaymentProcessor can be used
  • Interface Segregation: PaymentProcessor has focused interface
  • Dependency Inversion: Order depends on PaymentProcessor abstraction

Related Snippets