UML Class Diagrams and Relationships
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.
What is a UML Class Diagram?
Section titled “What is a UML Class Diagram?”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
Why Learn UML Class Diagrams?
Section titled “Why Learn UML Class Diagrams?”- 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
Basic Class Diagram Elements
Section titled “Basic Class Diagram Elements”Class Structure
Section titled “Class Structure”A class in UML has three compartments:
- Name - The class name
- Attributes - Variables/properties (with visibility:
+public,-private,#protected) - Methods - Functions/operations
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 passBasic Class Diagram
Section titled “Basic Class Diagram”UML Relationships: The Complete Guide
Section titled “UML Relationships: The Complete Guide”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
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!"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
Unidirectional Association
Section titled “Unidirectional Association”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 = nameBidirectional Association
Section titled “Bidirectional Association”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 studentsKey Points:
- Classes can exist independently
- Relationship can be one-way or two-way
- Usually implemented with references/pointers
- Weaker than composition/inheritance
3. Aggregation - “Has-A” (Weak)
Section titled “3. Aggregation - “Has-A” (Weak)”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
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 independentlystudent1 = 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 deletedKey Points:
- “Has-a” relationship
- Parts can exist independently
- Parts can belong to multiple wholes
- Weaker than composition
4. Composition - “Has-A” (Strong)
Section titled “4. Composition - “Has-A” (Strong)”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
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 Carcar = Car("Toyota", "Camry")# When car is deleted, engine and wheels are also goneKey Points:
- Strong “has-a” relationship
- Parts cannot exist without whole
- Parts belong to only one whole
- Lifecycle is tied together
5. Dependency - “Uses Temporarily”
Section titled “5. Dependency - “Uses Temporarily””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
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 calculatororder.print_receipt(printer) # Uses printerKey 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
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"Key Points:
- Contract-based relationship
- Implementing class must provide all abstract methods
- Enables polymorphism
- Loose coupling through interfaces
Relationship Comparison Table
Section titled “Relationship Comparison Table”| Relationship | Strength | Lifecycle | Multiplicity | When to Use |
|---|---|---|---|---|
| Inheritance | Strongest | Tied | One parent | ”Is-a” relationship, shared behavior |
| Composition | Very Strong | Tied | One-to-one/many | ”Part-of”, cannot exist alone |
| Aggregation | Moderate | Independent | One-to-many | ”Has-a”, can exist independently |
| Association | Moderate | Independent | Many-to-many | ”Uses-a”, communication |
| Dependency | Weakest | Independent | Temporary | Temporary use, method parameters |
| Realization | Moderate | Independent | Many-to-one | Implementing interfaces |
Multiplicity in Relationships
Section titled “Multiplicity in Relationships”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
Examples
Section titled “Examples”Visibility Modifiers
Section titled “Visibility Modifiers”- + Public - Accessible from anywhere
- - Private - Only accessible within the class
- # Protected - Accessible within class and subclasses
- ~ Package - Accessible within the same package
Complete Example: E-Commerce System
Section titled “Complete Example: E-Commerce System”Let’s design a complete e-commerce system showing all relationship types:
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.
Step 1: Identify Core Entities
Section titled “Step 1: Identify Core Entities”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
Step 2: Think About Relationships
Section titled “Step 2: Think About Relationships”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)
Step 3: Design the Classes
Section titled “Step 3: Design the Classes”from abc import ABC, abstractmethodfrom datetime import datetime, timedelta
# Base class using Inheritanceclass 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 Personclass 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 Personclass 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 Libraryclass 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 Memberclass 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 Loanclass 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 Booksclass 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) # AggregationStep 4: UML Class Diagram
Section titled “Step 4: UML Class Diagram”Step 5: Relationship Analysis
Section titled “Step 5: Relationship Analysis”Why these relationships?
-
Person → Member/Librarian (Inheritance)
- Both share common attributes (name, email)
- Both have a role but implement it differently
- True “is-a” relationship
-
Library → Book (Composition)
- Books are part of the library
- Books cannot exist without a library
- Strong ownership
-
Library → Member (Aggregation)
- Members can exist independently
- Members can belong to multiple libraries (in real world)
- Weaker relationship
-
Member → Loan (Composition)
- Loans are part of member’s borrowing history
- Loan cannot exist without a member
- Strong ownership
-
Loan → Book (Association)
- Loan references a book
- Book can exist without being loaned
- Communication relationship
-
Loan → Fine (Composition)
- Fine is part of the loan
- Fine cannot exist without a loan
- Optional but strong when present
-
Member → Book (Dependency)
- Member uses Book temporarily when borrowing
- No permanent storage
- Weakest relationship
Key Takeaways
Section titled “Key Takeaways”Choosing the Right Relationship
Section titled “Choosing the Right Relationship”-
Ask “Is-A”? → Use Inheritance
- Dog is-a Animal
- Car is-a Vehicle
-
Ask “Has-A” and “Can it exist alone?”
- No → Use Composition (Engine in Car)
- Yes → Use Aggregation (Student in University)
-
Ask “Uses temporarily?” → Use Dependency
- Method parameters
- Local variables
-
Ask “Needs to communicate?” → Use Association
- Classes that work together
- Bidirectional or unidirectional
-
Ask “Implements contract?” → Use Realization
- Implementing interfaces
- Abstract classes
Best Practices
Section titled “Best Practices”- Start with entities - Identify all classes first
- Identify relationships - Think about how classes relate
- Choose relationship type - Use the decision tree above
- Consider multiplicity - How many instances?
- Think about lifecycle - Who owns what?
- Keep it simple - Don’t overcomplicate relationships
- Document decisions - Add notes explaining complex relationships
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Practice Exercise
Section titled “Practice Exercise”Design a Restaurant Management System with:
- Restaurant, Menu, MenuItem, Order, OrderItem, Customer, Chef, Waiter, Payment
Think about:
- What are the core entities?
- What relationships exist between them?
- Which relationship type is appropriate for each?
- What is the multiplicity?
- Who owns what?
Draw the UML class diagram showing all relationships clearly labeled.