backend / theory foundations / 01_solid_principles.md

SOLID Principles in Python

4 interview angles 3 min read source

SOLID Principles in Python

The SOLID principles are a set of design principles in object-oriented programming that help developers create flexible, maintainable, and scalable software. Here’s an overview of the SOLID principles in the context of Python:


S: Single Responsibility Principle (SRP)

  • Definition: A class should have one, and only one, reason to change. In other words, it should have a single responsibility.
  • In Python: This means designing classes or functions that focus on a single task or purpose.

Example:

class ReportGenerator:
    def generate_report(self):
        print("Generating report...")

class EmailSender:
    def send_email(self):
        print("Sending email...")

# Each class has a single responsibility

O: Open/Closed Principle (OCP)

  • Definition: Software entities (classes, functions, modules) should be open for extension but closed for modification.
  • In Python: Use inheritance or composition to extend functionality without changing existing code.

Example:

class Notification:
    def send(self):
        pass

class EmailNotification(Notification):
    def send(self):
        print("Sending email notification...")

class SMSNotification(Notification):
    def send(self):
        print("Sending SMS notification...")

# New types of notifications can be added without modifying existing code

L: Liskov Substitution Principle (LSP)

  • Definition: Subtypes must be substitutable for their base types without altering the correctness of the program.
  • In Python: Ensure that subclasses override methods without changing their expected behavior.

Example:

class Bird:
    def fly(self):
        print("Flying...")

class Sparrow(Bird):
    def fly(self):
        print("Sparrow flying...")

# Subclass behavior is consistent with the base class

I: Interface Segregation Principle (ISP)

  • Definition: A class should not be forced to implement interfaces it does not use. Instead, break down large interfaces into smaller, more specific ones.
  • In Python: This can be achieved through multiple small, targeted methods or abstract base classes.

Example:

class Printer:
    def print_document(self):
        pass

class Scanner:
    def scan_document(self):
        pass

class MultiFunctionPrinter(Printer, Scanner):
    def print_document(self):
        print("Printing document...")

    def scan_document(self):
        print("Scanning document...")

# Separate interfaces for print and scan functionality

D: Dependency Inversion Principle (DIP)

  • Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • In Python: Use dependency injection or abstractions to decouple high-level and low-level components.

Example:

class NotificationSender:
    def __init__(self, notifier):
        self.notifier = notifier

    def send(self):
        self.notifier.send()

class EmailNotifier:
    def send(self):
        print("Sending email...")

class SMSNotifier:
    def send(self):
        print("Sending SMS...")

# Dependency Injection
notifier = EmailNotifier()
sender = NotificationSender(notifier)
sender.send()

By following the SOLID principles in Python, you can write clean, modular, and scalable code.


Interview angle

  • “Which SOLID principle matters most in Python?” - dependency inversion and single responsibility. Interface segregation is largely handled by duck typing and Protocols, and open/closed is less pressing in a dynamic language where you can compose behaviour freely.
  • “Give a real example of the single responsibility principle.” - a class that both computes a result and persists it has two reasons to change. Separate the calculation from the repository and each becomes independently testable.
  • “What does Liskov substitution actually forbid?” - a subclass that strengthens preconditions or weakens postconditions. The practical test: if callers need to know which subclass they hold, substitutability is broken and you should have used composition.
  • “Can SOLID be overapplied?” - yes. An interface per class, a factory per interface and a layer per concern produces indirection with no benefit. Apply a principle when you feel the pain it addresses.