Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Assign Responsibilities

Master responsibility assignment - the key to clean, maintainable code design!

Assigning responsibilities correctly is crucial for creating maintainable, testable, and scalable code. Poor responsibility assignment leads to god classes, tight coupling, and code that’s hard to understand and modify.

Diagram

A responsibility is a reason for a class to change. Each class should have one primary responsibility - one reason to exist and one reason to change.

  1. Data Management - Storing and managing data
  2. Business Logic - Implementing business rules
  3. Coordination - Coordinating between other classes
  4. Validation - Validating inputs and states
  5. Communication - Interacting with external systems
Diagram

Definition: A class should have only one reason to change.

What this means:

  • Each class should do one thing and do it well
  • If a class has multiple reasons to change, split it
  • Changes to one responsibility shouldn’t affect others
Diagram
  1. Identify what each entity needs to do
  2. Group related operations
  3. Assign to appropriate class
  4. Ensure single responsibility
  5. Check for cohesion

Entities Identified:

  • Book
  • Member
  • Loan
  • Reservation
  • Fine

Let’s assign responsibilities:

What does a Book need to do?

  • Store book information (title, author, ISBN)
  • Track availability status
  • Know its location in library

Book Responsibilities:

book.py
class Book:
# Data Management
def __init__(self, isbn: str, title: str, author: str):
self.isbn = isbn
self.title = title
self.author = author
self.is_available = True
# State Management
def mark_as_borrowed(self) -> None:
self.is_available = False
def mark_as_available(self) -> None:
self.is_available = True
def is_available(self) -> bool:
return self.is_available

Responsibility: Manage book data and availability state

What does a Loan need to do?

  • Store loan information (book, member, dates)
  • Calculate due date
  • Check if overdue
  • Calculate fine amount

Loan Responsibilities:

loan.py
class Loan:
def __init__(self, book: Book, member: Member, loan_date: datetime):
self.book = book
self.member = member
self.loan_date = loan_date
self.due_date = self._calculate_due_date()
self.return_date = None
# Business Logic
def _calculate_due_date(self) -> datetime:
return self.loan_date + timedelta(days=14)
def is_overdue(self) -> bool:
if self.return_date:
return False
return datetime.now() > self.due_date
def calculate_fine(self) -> float:
if not self.is_overdue():
return 0.0
days_overdue = (datetime.now() - self.due_date).days
return days_overdue * 1.0 # $1 per day
# State Management
def return_book(self) -> None:
self.return_date = datetime.now()

Responsibility: Manage loan lifecycle and calculate fines

classDiagram
    direction LR
    class Book {
        +StoreBookData()
        +TrackAvailability()
        +ManageState()
    }
    
    class Loan {
        +StoreLoanData()
        +CalculateDueDate()
        +CheckOverdue()
        +CalculateFine()
    }
    
    class Member {
        +StoreMemberData()
        +TrackMembership()
        +ValidateEligibility()
    }
    
    class Library {
        +ManageBooks()
        +ProcessLoans()
        +CoordinateOperations()
    }

Responsibility: Store and manage data

Example:

member.py
class Member:
def __init__(self, member_id: str, name: str, email: str):
self.member_id = member_id
self.name = name
self.email = email
self.membership_type = "STANDARD"
def update_membership(self, new_type: str) -> None:
self.membership_type = new_type

When to use: When you need to store data with minimal logic

Responsibility: Implement business rules and calculations

Example:

fine_calculator.py
class FineCalculator:
def calculate_fine(self, loan: Loan) -> float:
if not loan.is_overdue():
return 0.0
days_overdue = (datetime.now() - loan.due_date).days
# Business rule: $1 per day for first 7 days, $2 per day after
if days_overdue <= 7:
return days_overdue * 1.0
else:
return 7.0 + (days_overdue - 7) * 2.0

When to use: When you have complex business rules

Responsibility: Coordinate between multiple classes

Example:

library_service.py
class LibraryService:
def __init__(self, book_repository: BookRepository,
loan_repository: LoanRepository,
fine_calculator: FineCalculator):
self.book_repository = book_repository
self.loan_repository = loan_repository
self.fine_calculator = fine_calculator
def borrow_book(self, book_id: str, member_id: str) -> Loan:
# Coordinate between multiple classes
book = self.book_repository.find_by_id(book_id)
member = self.member_repository.find_by_id(member_id)
if not book.is_available():
raise ValueError("Book not available")
loan = Loan(book, member, datetime.now())
self.loan_repository.save(loan)
book.mark_as_borrowed()
return loan

When to use: When you need to coordinate multiple operations

Responsibility: Validate inputs and states

Example:

loan_validator.py
class LoanValidator:
def validate_loan(self, book: Book, member: Member) -> bool:
if not book.is_available():
raise ValueError("Book is not available")
if member.has_overdue_books():
raise ValueError("Member has overdue books")
if member.get_active_loans_count() >= 5:
raise ValueError("Member has reached loan limit")
return True

When to use: When you need to validate before operations

Responsibility: Handle data persistence

Example:

book_repository.py
class BookRepository:
def save(self, book: Book) -> None:
# Save to database
pass
def find_by_id(self, book_id: str) -> Book:
# Retrieve from database
pass
def find_available_books(self) -> List[Book]:
# Query database
pass

When to use: When you need to abstract data access


Part 4: Common Mistakes and How to Avoid Them

Section titled “Part 4: Common Mistakes and How to Avoid Them”

Mistake 1: God Class (Too Many Responsibilities)

Section titled “Mistake 1: God Class (Too Many Responsibilities)”

Bad Example:

god_class_bad.py
class LibraryManager:
# Too many responsibilities!
def add_book(self, book: Book): pass
def remove_book(self, book_id: str): pass
def borrow_book(self, book_id: str, member_id: str): pass
def return_book(self, loan_id: str): pass
def calculate_fine(self, loan_id: str): float: pass
def send_notification(self, member_id: str, message: str): pass
def generate_report(self) -> Report: pass
def process_payment(self, member_id: str, amount: float): pass

Problems:

  • Too many reasons to change
  • Hard to test
  • Hard to maintain
  • Violates SRP

Good Example:

god_class_good.py
class BookService:
def add_book(self, book: Book): pass
def remove_book(self, book_id: str): pass
class LoanService:
def borrow_book(self, book_id: str, member_id: str): pass
def return_book(self, loan_id: str): pass
class FineService:
def calculate_fine(self, loan_id: str) -> float: pass
class NotificationService:
def send_notification(self, member_id: str, message: str): pass
class ReportService:
def generate_report(self) -> Report: pass
class PaymentService:
def process_payment(self, member_id: str, amount: float): pass
Diagram

Mistake 2: Anemic Domain Model (Too Few Responsibilities)

Section titled “Mistake 2: Anemic Domain Model (Too Few Responsibilities)”

Bad Example:

anemic_bad.py
class Book:
# Only data, no behavior!
def __init__(self, isbn: str, title: str):
self.isbn = isbn
self.title = title
self.is_available = True
class BookManager: # All logic in manager
def mark_as_borrowed(self, book: Book):
book.is_available = False
def mark_as_available(self, book: Book):
book.is_available = True

Problems:

  • Data and behavior separated
  • Objects are just data containers
  • Logic scattered across managers

Good Example:

anemic_good.py
class Book:
def __init__(self, isbn: str, title: str):
self.isbn = isbn
self.title = title
self._is_available = True
# Behavior with data
def mark_as_borrowed(self) -> None:
self._is_available = False
def mark_as_available(self) -> None:
self._is_available = True
def is_available(self) -> bool:
return self._is_available

Bad Example:

wrong_class_bad.py
class Book:
def send_overdue_notification(self, member: Member):
# Book shouldn't send notifications!
email_service.send(member.email, "Book is overdue")

Problem: Book is responsible for sending notifications (should be NotificationService)

Good Example:

wrong_class_good.py
class Book:
def is_overdue(self) -> bool:
return datetime.now() > self.due_date
class NotificationService:
def send_overdue_notification(self, book: Book, member: Member):
if book.is_overdue():
email_service.send(member.email, "Book is overdue")

Part 5: Responsibility Assignment Framework

Section titled “Part 5: Responsibility Assignment Framework”
graph TD
    A[What needs to be done?] --> B{Is it data management?}
    B -->|Yes| C[Data Holder/Repository]
    B -->|No| D{Is it business logic?}
    D -->|Yes| E[Business Logic Class]
    D -->|No| F{Is it coordination?}
    F -->|Yes| G[Service/Coordinator]
    F -->|No| H{Is it validation?}
    H -->|Yes| I[Validator]
    H -->|No| J{Is it communication?}
    J -->|Yes| K[Service/Adapter]
    
    style C fill:#dbeafe
    style E fill:#d1fae5
    style G fill:#fef3c7
    style I fill:#fee2e2
    style K fill:#e5e7eb

For each responsibility, ask:

  1. Who owns this data? → Assign to that class
  2. What is the primary purpose? → Assign to class with that purpose
  3. Does it need coordination? → Create a coordinator/service
  4. Is it reusable? → Create a separate utility/service
  5. Does it violate SRP? → Split into multiple classes

Entities:

  • ParkingLot
  • ParkingSpot
  • Vehicle
  • Ticket
  • Payment

Responsibility Assignment:

What should ParkingLot do?

  • Manage collection of parking spots
  • Find available spots
  • Assign spots to vehicles
  • Release spots when vehicles leave

ParkingLot Class:

parking_lot.py
class ParkingLot:
def __init__(self, capacity: int):
self.spots: List[ParkingSpot] = []
self.capacity = capacity
# Coordination responsibility
def park_vehicle(self, vehicle: Vehicle) -> Ticket:
spot = self._find_available_spot(vehicle.get_type())
if not spot:
raise ValueError("No available spots")
spot.park_vehicle(vehicle)
return Ticket(vehicle, spot, datetime.now())
def unpark_vehicle(self, ticket: Ticket) -> Payment:
spot = ticket.spot
vehicle = spot.unpark_vehicle()
duration = ticket.calculate_duration()
amount = self._calculate_payment(duration, vehicle.get_type())
return Payment(ticket, amount)
# Helper methods
def _find_available_spot(self, vehicle_type: VehicleType) -> Optional[ParkingSpot]:
for spot in self.spots:
if spot.is_available() and spot.get_type() == vehicle_type:
return spot
return None
def _calculate_payment(self, duration: float, vehicle_type: VehicleType) -> float:
base_rate = 10.0 # $10 per hour
if vehicle_type == VehicleType.TRUCK:
base_rate *= 1.5 # Trucks pay 50% more
return duration * base_rate

Responsibility: Coordinate parking operations

What should ParkingSpot do?

  • Track its own state (occupied/available)
  • Know its type and location
  • Park/unpark vehicles

ParkingSpot Class:

parking_spot.py
class ParkingSpot:
def __init__(self, spot_id: str, spot_type: VehicleType):
self.spot_id = spot_id
self.spot_type = spot_type
self._is_occupied = False
self._vehicle: Optional[Vehicle] = None
# State management responsibility
def park_vehicle(self, vehicle: Vehicle) -> None:
if not self.is_available():
raise ValueError("Spot is already occupied")
if vehicle.get_type() != self.spot_type:
raise ValueError("Vehicle type doesn't match spot type")
self._vehicle = vehicle
self._is_occupied = True
def unpark_vehicle(self) -> Vehicle:
if not self._is_occupied:
raise ValueError("Spot is not occupied")
vehicle = self._vehicle
self._vehicle = None
self._is_occupied = False
return vehicle
def is_available(self) -> bool:
return not self._is_occupied
def get_type(self) -> VehicleType:
return self.spot_type

Responsibility: Manage spot state and vehicle assignment

What should Ticket do?

  • Store entry information
  • Calculate parking duration
  • Reference vehicle and spot

Ticket Class:

ticket.py
class Ticket:
def __init__(self, vehicle: Vehicle, spot: ParkingSpot, entry_time: datetime):
self.ticket_id = self._generate_id()
self.vehicle = vehicle
self.spot = spot
self.entry_time = entry_time
# Business logic responsibility
def calculate_duration(self) -> float:
return (datetime.now() - self.entry_time).total_seconds() / 3600
def _generate_id(self) -> str:
return f"TICKET_{uuid.uuid4().hex[:8]}"

Responsibility: Track parking session and calculate duration

Visual: Complete Responsibility Assignment

Section titled “Visual: Complete Responsibility Assignment”
classDiagram
    direction LR
    class ParkingLot {
        +ManageSpots()
        +CoordinateParking()
        +CalculatePayment()
    }

    class ParkingSpot {
        +TrackState()
        +ParkUnparkVehicles()
        +ValidateVehicleType()
    }

    class Ticket {
        +StoreEntryInfo()
        +CalculateDuration()
        +ReferenceVehicleSpot()
    }

    class Payment {
        +StorePaymentInfo()
        +ProcessPayment()
        +ReferenceTicket()
    }

Cohesion measures how related the responsibilities within a class are.

Types:

  • High Cohesion - All methods work together for one purpose
  • Low Cohesion - Methods are unrelated
Diagram

Coupling measures how dependent classes are on each other.

Types:

  • Low Coupling - Classes are independent
  • High Coupling - Classes are tightly dependent
Diagram

Aim for:


  • Single Responsibility Principle - One reason to change
  • Identify what each class needs to do - Group related operations
  • Avoid God Classes - Split large classes
  • Avoid Anemic Models - Include behavior with data
  • High Cohesion - Related responsibilities together
  • Low Coupling - Independent classes
  • Use patterns - Data Holder, Business Logic, Coordinator, Validator, Repository

When assigning responsibilities, ensure:

  • Each class has one primary responsibility
  • Responsibilities are related (high cohesion)
  • Classes are independent (low coupling)
  • No God Classes - Split if too many responsibilities
  • No Anemic Models - Include behavior with data
  • Clear purpose - Easy to understand what class does
Diagram

Now that you’ve mastered assigning responsibilities, let’s learn how to create class diagrams:

Next: Class Diagrams →

This next guide will teach you how to visualize your design with class diagrams, showing relationships, inheritance, and composition!