SOLID Principles

S – Single Responsibility Principle (SRP)

A class should have only one reason to change — one job or responsibility.

Python Example:

# Violates SRP: User handles persistence + validation + notification
class User:
    def save_to_db(self): ...      # Persistence
    def validate_email(self): ...  # Validation
    def send_welcome_email(self): ...  # Notification

# Follows SRP: Separate responsibilities
class User:
    def __init__(self, name, email): ...

class UserRepository:
    def save(self, user): ...

class UserValidator:
    def is_valid(self, user): ...

class EmailService:
    def send_welcome(self, user): ...

O – Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification.

Python Example:

# Violates OCP: Modify class to add new shape
class AreaCalculator:
    def area(self, shape):
        if shape.type == "circle":
            return 3.14 * shape.radius ** 2
        elif shape.type == "square":  # New type → modify existing code
            return shape.side ** 2

# Follows OCP: Extend with new class, no modification
from abc import ABC, abstractmethod
class Shape(ABC):
    @abstractmethod
    def area(self): ...

class Circle(Shape):
    def area(self): return 3.14 * self.radius ** 2

class Square(Shape):
    def area(self): return self.side ** 2

class AreaCalculator:
    def area(self, shape: Shape):  # Works with any new Shape
        return shape.area()

L – Liskov Substitution Principle (LSP)

Subclasses should be substitutable for their base classes without breaking the program.

Python Example:

# Violates LSP: Square forces side equality, breaks Rectangle expectation
class Rectangle:
    def set_width(self, w): self.width = w
    def set_height(self, h): self.height = h
    def area(self): return self.width * self.height

class Square(Rectangle):  # Bad inheritance
    def set_width(self, w): self.width = self.height = w
    def set_height(self, h): self.width = self.height = h

# Follows LSP: Separate hierarchy or composition
class Square:
    def __init__(self, side): self.side = side
    def area(self): return self.side ** 2

I – Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use — prefer small, specific interfaces.

Python Example:

# Violates ISP: Printer forces unused fax method
class AllInOnePrinter:
    def print(self): ...
    def scan(self): ...
    def fax(self): ...  # Scanner-only clients forced to implement

# Follows ISP: Separate interfaces
class Printer:
    def print(self): ...

class Scanner:
    def scan(self): ...

class MultiFunctionDevice(Printer, Scanner):
    def print(self): ...
    def scan(self): ...

D – Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules; both should depend on abstractions (interfaces).

Python Example:

# Violates DIP: High-level depends on concrete low-level
class MySQLDatabase:  # Low-level
    def save(self): ...

class UserService:  # High-level depends directly
    def __init__(self):
        self.db = MySQLDatabase()  # Tight coupling

# Follows DIP: Depend on abstraction
from abc import ABC, abstractmethod
class Database(ABC):
    @abstractmethod
    def save(self): ...

class MySQLDatabase(Database):
    def save(self): ...

class UserService:
    def __init__(self, db: Database):  # Inject abstraction
        self.db = db

These SOLID principles ensure maintainable, scalable, and testable code by promoting flexibility and reducing coupling. Apply them consistently for robust software design.