Skip to content

UML Class Diagrams and Relationships

Master UML class diagrams and relationships for better system design.

UML (Unified Modeling Language) Class Diagrams are the foundation of object-oriented design. They help you visualize the structure of your system, understand relationships between classes, and communicate your design effectively.

A Class Diagram shows:

  • Classes - Blueprints for objects
  • Attributes - Data/properties of classes
  • Methods - Operations/behaviors of classes
  • Relationships - How classes relate to each other
  • Design - Think through your system structure before coding
  • Communication - Visual representation is clearer than text
  • Documentation - Living documentation of your system
  • Interviews - Essential for LLD (Low-Level Design) interviews
  • Refactoring - Understand existing codebases better

A class in UML has three compartments:

  1. Name - The class name
  2. Attributes - Variables/properties (with visibility: + public, - private, # protected)
  3. Methods - Functions/operations
user_example.py
class User:
def __init__(self, username: str, email: str):
self.username = username # Public attribute
self._email = email # Protected attribute
self.__password = None # Private attribute
def login(self): # Public method
pass
def _validate(self): # Protected method
pass
Diagram

Understanding relationships is crucial for good design. Let’s explore each relationship type in detail.

1. Inheritance (Generalization) - “Is-A”

Section titled “1. Inheritance (Generalization) - “Is-A””

Symbol: Solid line with hollow triangle arrow
Meaning: One class is a specialized version of another
Python: Class inheritance using class Child(Parent)

When to use:

  • When you have a true “is-a” relationship
  • When child classes share common behavior with parent
  • When you need polymorphism
inheritance_example.py
class Animal:
def eat(self):
return "Eating..."
def sleep(self):
return "Sleeping..."
class Dog(Animal):
"""Dog IS-A Animal"""
def bark(self):
return "Woof!"
class Cat(Animal):
"""Cat IS-A Animal"""
def meow(self):
return "Meow!"
Diagram

Key Points:

  • Child inherits all attributes and methods from parent
  • Child can override parent methods
  • Child can add new attributes and methods
  • Strong coupling - changes to parent affect children

2. Association - “Uses-A” or “Knows-A”

Section titled “2. Association - “Uses-A” or “Knows-A””

Symbol: Solid line with arrow (optional)
Meaning: Classes know about each other and can use each other
Python: One class has a reference to another

When to use:

  • When classes need to communicate
  • When one class uses another’s services
  • Loose coupling - classes can exist independently
association_example.py
class Teacher:
def __init__(self, name: str):
self.name = name
self.courses = [] # Association with Course
def teach(self, course):
"""Teacher uses Course"""
self.courses.append(course)
return f"{self.name} is teaching {course.name}"
class Course:
def __init__(self, name: str):
self.name = name
Diagram
bidirectional_association.py
class Student:
def __init__(self, name: str):
self.name = name
self.courses = [] # Student knows about courses
def enroll(self, course):
self.courses.append(course)
course.students.append(self) # Course knows about student
class Course:
def __init__(self, name: str):
self.name = name
self.students = [] # Course knows about students
Diagram

Key Points:

  • Classes can exist independently
  • Relationship can be one-way or two-way
  • Usually implemented with references/pointers
  • Weaker than composition/inheritance

Symbol: Hollow diamond on the “whole” side
Meaning: Part-of relationship where parts can exist independently
Python: One class contains another, but the contained class can exist without the container

When to use:

  • When parts can belong to multiple wholes
  • When parts can exist independently
  • “Part-of” relationship that’s not essential
aggregation_example.py
class University:
def __init__(self, name: str):
self.name = name
self.students = [] # Aggregation - students can exist without university
def add_student(self, student):
self.students.append(student)
class Student:
def __init__(self, name: str, student_id: str):
self.name = name
self.student_id = student_id
# Student can exist without being in a university
# Students can exist independently
student1 = Student("Alice", "S001")
student2 = Student("Bob", "S002")
university = University("MIT")
university.add_student(student1)
university.add_student(student2)
# Students still exist even if university is deleted
Diagram

Key Points:

  • “Has-a” relationship
  • Parts can exist independently
  • Parts can belong to multiple wholes
  • Weaker than composition

Symbol: Filled diamond on the “whole” side
Meaning: Strong “part-of” relationship where parts cannot exist without the whole
Python: One class contains another, and the contained class’s lifecycle is managed by the container

When to use:

  • When parts cannot exist without the whole
  • When parts belong to only one whole
  • Strong ownership relationship
composition_example.py
class Car:
def __init__(self, brand: str, model: str):
self.brand = brand
self.model = model
# Composition - Engine cannot exist without Car
self.engine = Engine() # Created when Car is created
self.wheels = [Wheel() for _ in range(4)] # Created with Car
def start(self):
return self.engine.start()
class Engine:
def __init__(self):
self.running = False
def start(self):
self.running = True
return "Engine started"
class Wheel:
def __init__(self):
self.size = 16
# Engine and Wheels are created and destroyed with Car
car = Car("Toyota", "Camry")
# When car is deleted, engine and wheels are also gone
Diagram

Key Points:

  • Strong “has-a” relationship
  • Parts cannot exist without whole
  • Parts belong to only one whole
  • Lifecycle is tied together

Symbol: Dashed arrow
Meaning: One class uses another temporarily, but doesn’t own it
Python: Method parameter, local variable, or return type

When to use:

  • When a class uses another class temporarily
  • When passing objects as parameters
  • Weakest relationship - no ownership
dependency_example.py
class Order:
def __init__(self, order_id: str):
self.order_id = order_id
self.total = 0
def calculate_total(self, calculator):
"""Dependency - uses Calculator temporarily"""
# Calculator is passed in, not stored
self.total = calculator.add(100, 50)
return self.total
def print_receipt(self, printer):
"""Dependency - uses Printer temporarily"""
printer.print(f"Order {self.order_id}: ${self.total}")
class Calculator:
def add(self, a, b):
return a + b
class Printer:
def print(self, text):
print(text)
order = Order("ORD-001")
calculator = Calculator()
printer = Printer()
order.calculate_total(calculator) # Uses calculator
order.print_receipt(printer) # Uses printer
Diagram

Key Points:

  • Weakest relationship
  • Temporary use only
  • No ownership or storage
  • Usually method parameters

6. Realization/Implementation - “Implements”

Section titled “6. Realization/Implementation - “Implements””

Symbol: Dashed line with hollow triangle arrow
Meaning: A class implements an interface or abstract class
Python: Implementing abstract methods from ABC or Protocol

When to use:

  • When implementing interfaces
  • When implementing abstract classes
  • Contract-based design
realization_example.py
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Interface/Abstract class"""
@abstractmethod
def process_payment(self, amount: float):
pass
class CreditCardProcessor(PaymentProcessor):
"""Implements PaymentProcessor"""
def process_payment(self, amount: float):
return f"Processing ${amount} via credit card"
class PayPalProcessor(PaymentProcessor):
"""Implements PaymentProcessor"""
def process_payment(self, amount: float):
return f"Processing ${amount} via PayPal"
Diagram

Key Points:

  • Contract-based relationship
  • Implementing class must provide all abstract methods
  • Enables polymorphism
  • Loose coupling through interfaces

RelationshipStrengthLifecycleMultiplicityWhen to Use
InheritanceStrongestTiedOne parent”Is-a” relationship, shared behavior
CompositionVery StrongTiedOne-to-one/many”Part-of”, cannot exist alone
AggregationModerateIndependentOne-to-many”Has-a”, can exist independently
AssociationModerateIndependentMany-to-many”Uses-a”, communication
DependencyWeakestIndependentTemporaryTemporary use, method parameters
RealizationModerateIndependentMany-to-oneImplementing interfaces

Multiplicity shows how many instances participate in a relationship:

  • 1 - Exactly one
  • 0..1 - Zero or one (optional)
  • 1..* or 1..n - One or more
  • 0..* or 0..n - Zero or more
  • * or n - Many (zero or more)
  • m..n - Between m and n
Diagram
  • + Public - Accessible from anywhere
  • - Private - Only accessible within the class
  • # Protected - Accessible within class and subclasses
  • ~ Package - Accessible within the same package
Diagram

Let’s design a complete e-commerce system showing all relationship types:

Diagram

Comprehensive Example: Library Management System

Section titled “Comprehensive Example: Library Management System”

Let’s walk through designing a Library Management System step by step, showing how to think about relationships.

First, identify the main entities:

  • Library - The main system
  • Book - Items in the library
  • Member - People who borrow books
  • Librarian - Staff who manage the library
  • Loan - Record of borrowing
  • Fine - Penalty for late returns

Library and Book:

  • Library has Books (many books)
  • Books can exist without a library? No - they’re part of the library
  • → Composition (strong has-a)

Member and Loan:

  • Member creates Loans (many loans)
  • Loan cannot exist without Member
  • → Composition (loan is part of member’s borrowing history)

Loan and Book:

  • Loan references a Book
  • Book can exist without a Loan
  • → Association (loan uses book)

Member and Librarian:

  • Both are types of Person
  • → Inheritance (is-a relationship)

Loan and Fine:

  • Loan may have a Fine (optional)
  • Fine cannot exist without Loan
  • → Composition (fine is part of loan)
library_system.py
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
# Base class using Inheritance
class Person(ABC):
def __init__(self, name: str, email: str):
self.name = name
self.email = email
@abstractmethod
def get_role(self):
pass
# Inheritance - Member IS-A Person
class Member(Person):
def __init__(self, name: str, email: str, member_id: str):
super().__init__(name, email)
self.member_id = member_id
self.loans = [] # Composition - loans belong to member
def get_role(self):
return "Member"
def borrow_book(self, book, librarian):
# Dependency - uses Book and Librarian temporarily
loan = Loan(self, book, librarian)
self.loans.append(loan) # Composition
return loan
# Inheritance - Librarian IS-A Person
class Librarian(Person):
def __init__(self, name: str, email: str, employee_id: str):
super().__init__(name, email)
self.employee_id = employee_id
def get_role(self):
return "Librarian"
def process_return(self, loan):
# Dependency - uses Loan temporarily
loan.return_book()
if loan.is_overdue():
fine = Fine(loan) # Composition - fine created with loan
loan.fine = fine
# Composition - Book is part of Library
class Book:
def __init__(self, isbn: str, title: str, author: str):
self.isbn = isbn
self.title = title
self.author = author
self.available = True
# Composition - Loan belongs to Member
class Loan:
def __init__(self, member, book, librarian):
self.member = member # Association - references member
self.book = book # Association - references book
self.librarian = librarian # Association - references librarian
self.borrow_date = datetime.now()
self.due_date = self.borrow_date + timedelta(days=14)
self.return_date = None
self.fine = None # Composition - optional fine
def return_book(self):
self.return_date = datetime.now()
self.book.available = True
def is_overdue(self):
if self.return_date:
return self.return_date > self.due_date
return datetime.now() > self.due_date
# Composition - Fine belongs to Loan
class Fine:
def __init__(self, loan):
self.loan = loan # Association - references loan
days_overdue = (datetime.now() - loan.due_date).days
self.amount = days_overdue * 0.50 # $0.50 per day
# Composition - Library contains Books
class Library:
def __init__(self, name: str):
self.name = name
self.books = [] # Composition - books belong to library
self.members = [] # Aggregation - members can exist independently
self.librarians = [] # Aggregation - librarians can exist independently
def add_book(self, book):
self.books.append(book) # Composition
def register_member(self, member):
self.members.append(member) # Aggregation
def hire_librarian(self, librarian):
self.librarians.append(librarian) # Aggregation
Diagram

Why these relationships?

  1. Person → Member/Librarian (Inheritance)

    • Both share common attributes (name, email)
    • Both have a role but implement it differently
    • True “is-a” relationship
  2. Library → Book (Composition)

    • Books are part of the library
    • Books cannot exist without a library
    • Strong ownership
  3. Library → Member (Aggregation)

    • Members can exist independently
    • Members can belong to multiple libraries (in real world)
    • Weaker relationship
  4. Member → Loan (Composition)

    • Loans are part of member’s borrowing history
    • Loan cannot exist without a member
    • Strong ownership
  5. Loan → Book (Association)

    • Loan references a book
    • Book can exist without being loaned
    • Communication relationship
  6. Loan → Fine (Composition)

    • Fine is part of the loan
    • Fine cannot exist without a loan
    • Optional but strong when present
  7. Member → Book (Dependency)

    • Member uses Book temporarily when borrowing
    • No permanent storage
    • Weakest relationship

  1. Ask “Is-A”? → Use Inheritance

    • Dog is-a Animal
    • Car is-a Vehicle
  2. Ask “Has-A” and “Can it exist alone?”

    • No → Use Composition (Engine in Car)
    • Yes → Use Aggregation (Student in University)
  3. Ask “Uses temporarily?” → Use Dependency

    • Method parameters
    • Local variables
  4. Ask “Needs to communicate?” → Use Association

    • Classes that work together
    • Bidirectional or unidirectional
  5. Ask “Implements contract?” → Use Realization

    • Implementing interfaces
    • Abstract classes
  1. Start with entities - Identify all classes first
  2. Identify relationships - Think about how classes relate
  3. Choose relationship type - Use the decision tree above
  4. Consider multiplicity - How many instances?
  5. Think about lifecycle - Who owns what?
  6. Keep it simple - Don’t overcomplicate relationships
  7. Document decisions - Add notes explaining complex relationships

Design a Restaurant Management System with:

  • Restaurant, Menu, MenuItem, Order, OrderItem, Customer, Chef, Waiter, Payment

Think about:

  1. What are the core entities?
  2. What relationships exist between them?
  3. Which relationship type is appropriate for each?
  4. What is the multiplicity?
  5. Who owns what?

Draw the UML class diagram showing all relationships clearly labeled.