If you follow these principles, you’ll find it way easier to develop and modify software.
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
The SOLID principles help you separate responsibilities, making changes smoother while steering clear of mistakes. By applying these guidelines, your Python code will be cleaner and easier to maintain.
Let’s break down each principle simply, complete with examples in Python!
Python Code for SOLID Principles: Library Management System
from abc import ABC, abstractmethod
from typing import List
# Single Responsibility Principle (SRP)
# The Book class is responsible only for storing information about books.
class Book:
def __init__(self, book_id: int, title: str, author: str):
self.book_id = book_id
self.title = title
self.author = author
# Open/Closed Principle (OCP)
# The FineCalculator class is open to extension but closed to modification.
class FineCalculator(ABC):
@abstractmethod
def calculate_fine(self, days_late: int) -> float:
pass
class StandardFineCalculator(FineCalculator):
def calculate_fine(self, days_late: int) -> float:
return days_late * 0.5 # $0.5 per day late
class PremiumFineCalculator(FineCalculator):
def calculate_fine(self, days_late: int) -> float:
return days_late * 1.0 # $1.0 per day late
# Liskov Substitution Principle (LSP)
# Subclasses of FineCalculator (StandardFineCalculator, PremiumFineCalculator) can replace the base class.
def process_fine(calculator: FineCalculator, days_late: int):
fine = calculator.calculate_fine(days_late)
print(f"The fine for {days_late} days late is: ${fine:.2f}")
# Interface Segregation Principle (ISP)
# Interfaces are divided so that users implement only what they need.
class BorrowingService(ABC):
@abstractmethod
def borrow_book(self, book_id: int, user_id: int):
pass
@abstractmethod
def return_book(self, book_id: int, user_id: int):
pass
class UserManagementService(ABC):
@abstractmethod
def add_user(self, user_id: int, name: str):
pass
@abstractmethod
def remove_user(self, user_id: int):
pass
class LibraryService(BorrowingService, UserManagementService):
def __init__(self):
self.borrowed_books = {} # {book_id: user_id}
self.users = {} # {user_id: name}
def borrow_book(self, book_id: int, user_id: int):
if book_id in self.borrowed_books:
print(f"Book {book_id} is already borrowed.")
else:
self.borrowed_books[book_id] = user_id
print(f"User {user_id} borrowed book {book_id}.")
def return_book(self, book_id: int, user_id: int):
if book_id in self.borrowed_books and self.borrowed_books[book_id] == user_id:
del self.borrowed_books[book_id]
print(f"User {user_id} returned book {book_id}.")
else:
print(f"Book {book_id} was not borrowed by user {user_id}.")
def add_user(self, user_id: int, name: str):
self.users[user_id] = name
print(f"User {name} added with ID {user_id}.")
def remove_user(self, user_id: int):
if user_id in self.users:
del self.users[user_id]
print(f"User with ID {user_id} removed.")
else:
print(f"User with ID {user_id} not found.")
# Dependency Inversion Principle (DIP)
# High-level modules (LibraryManager) depend on abstractions (FineCalculator, BorrowingService).
class LibraryManager:
def __init__(self, fine_calculator: FineCalculator, library_service: LibraryService):
self.fine_calculator = fine_calculator
self.library_service = library_service
def issue_fine(self, days_late: int):
fine = self.fine_calculator.calculate_fine(days_late)
print(f"Issuing a fine of: ${fine:.2f}")
def borrow_book(self, book_id: int, user_id: int):
self.library_service.borrow_book(book_id, user_id)
def return_book(self, book_id: int, user_id: int):
self.library_service.return_book(book_id, user_id)
# Demonstration
if __name__ == "__main__":
# Create a library service and fine calculator (SRP, DIP)
library_service = LibraryService()
fine_calculator = StandardFineCalculator()
library_manager = LibraryManager(fine_calculator, library_service)
# Manage users (SRP, ISP)
library_service.add_user(1, "Alice")
library_service.add_user(2, "Bob")
# Borrow and return books (ISP, DIP)
library_manager.borrow_book(101, 1)
library_manager.return_book(101, 1)
# Calculate fines (OCP, LSP)
process_fine(fine_calculator, 3)
# Switch to a premium fine calculator (DIP)
premium_calculator = PremiumFineCalculator()
library_manager = LibraryManager(premium_calculator, library_service)
library_manager.issue_fine(3)
Explanation of SOLID Principles in the Code:
- SRP (Single Responsibility Principle):
Bookclass only handles book data.LibraryServicehandles book borrowing, returning, and user management.
- OCP (Open/Closed Principle):
FineCalculatorallows for extension with different types of fine calculations (e.g.,StandardFineCalculatorandPremiumFineCalculator) without modifying existing code.
- LSP (Liskov Substitution Principle):
- Functions like
process_finework with any subclass ofFineCalculatorbecause all adhere to the same interface.
- Functions like
- ISP (Interface Segregation Principle):
BorrowingServiceandUserManagementServiceare separate interfaces, ensuring that a class only implements what it needs.
- DIP (Dependency Inversion Principle):
LibraryManagerdepends on abstractions (FineCalculatorandLibraryService) instead of concrete implementations, allowing easy replacement or extension.
