SOLID Principles for Writing Quality, Sustainable, and Scalable Code

If you follow these principles, you’ll find it way easier to develop and modify software.

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:

  1. SRP (Single Responsibility Principle):
    • Book class only handles book data.
    • LibraryService handles book borrowing, returning, and user management.
  2. OCP (Open/Closed Principle):
    • FineCalculator allows for extension with different types of fine calculations (e.g., StandardFineCalculator and PremiumFineCalculator) without modifying existing code.
  3. LSP (Liskov Substitution Principle):
    • Functions like process_fine work with any subclass of FineCalculator because all adhere to the same interface.
  4. ISP (Interface Segregation Principle):
    • BorrowingService and UserManagementService are separate interfaces, ensuring that a class only implements what it needs.
  5. DIP (Dependency Inversion Principle):
    • LibraryManager depends on abstractions (FineCalculator and LibraryService) instead of concrete implementations, allowing easy replacement or extension.

About the author

Vili M, PhD

With an extensive experience in programming, Vili has dedicated his career to developing innovative solutions and advancing technology. As an expert in programming, electromagnetic fields, robotics, and teaching skills, he combines academic knowledge with practical expertise to deliver impactful results.