Skip to content

SOLID Principles - Universal Software Engineering Standards

Overview

SOLID principles are fundamental design principles that guide software development across all programming languages and frameworks. These principles help create maintainable, scalable, and robust code.

graph TD
    SOLID[SOLID Principles]

    SOLID --> S[Single Responsibility]
    SOLID --> O[Open/Closed]
    SOLID --> L[Liskov Substitution]
    SOLID --> I[Interface Segregation]
    SOLID --> D[Dependency Inversion]

    S --> S1["One reason to change"]
    S --> S2["Focused purpose"]

    O --> O1["Open for extension"]
    O --> O2["Closed for modification"]

    L --> L1["Substitutable objects"]
    L --> L2["Inheritance contracts"]

    I --> I1["Specific interfaces"]
    I --> I2["No forced dependencies"]

    D --> D1["Abstractions over concretions"]
    D --> D2["Flexible dependencies"]

Single Responsibility Principle (SRP)

Definition

A class should have one, and only one, reason to change. Each class should have a single responsibility or purpose.

Why It Matters

  • Easier to understand and maintain
  • Reduces coupling between different concerns
  • Makes code more testable and modular
  • Prevents "god classes" that do everything

Examples

❌ Violation Example

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def save_to_database(self):
        # Database logic here
        pass

    def send_welcome_email(self):
        # Email sending logic here
        pass

    def validate_email(self):
        # Email validation logic here
        pass

✅ SRP Better Approach

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

class UserRepository:
    def save(self, user):
        # Database logic here
        pass

class EmailService:
    def send_welcome_email(self, user):
        # Email sending logic here
        pass

class EmailValidator:
    def validate(self, email):
        # Email validation logic here
        pass

Open/Closed Principle (OCP)

Definition

Software entities should be open for extension but closed for modification. You should be able to extend behavior without modifying existing code.

Why It Matters

  • Prevents breaking existing functionality
  • Enables flexible and extensible systems
  • Reduces risk when adding new features
  • Supports plugin architectures

Examples

❌ Violation Example

class PaymentProcessor:
    def process_payment(self, payment_type, amount):
        if payment_type == "credit_card":
            # Credit card processing logic
            pass
        elif payment_type == "paypal":
            # PayPal processing logic
            pass
        elif payment_type == "bitcoin":  # Added later, modified existing code
            # Bitcoin processing logic
            pass

✅ OCP Better Approach

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):
        # Credit card processing logic
        pass

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):
        # PayPal processing logic
        pass

class BitcoinProcessor(PaymentProcessor):  # Extended without modification
    def process_payment(self, amount):
        # Bitcoin processing logic
        pass

Liskov Substitution Principle (LSP)

Definition

Objects of a superclass should be replaceable with objects of a subclass without breaking the application. Subtypes must be substitutable for their base types.

Why It Matters

  • Ensures inheritance hierarchies work correctly
  • Prevents unexpected behavior in polymorphic code
  • Maintains contract integrity
  • Enables safe refactoring

Examples

❌ Violation Example

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):  # Violates LSP
    def set_width(self, width):
        self.width = width
        self.height = width  # Side effect - changes behavior

    def set_height(self, height):
        self.width = height  # Side effect - changes behavior
        self.height = height

✅ LSP Better Approach

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side * self.side

Interface Segregation Principle (ISP)

Definition

Clients should not be forced to depend on interfaces they don't use. Create specific, focused interfaces rather than large, general-purpose ones.

Why It Matters

  • Reduces coupling between components
  • Makes code more modular and flexible
  • Prevents interface pollution
  • Easier to test and mock

Examples

❌ Violation Example

class Worker:
    def work(self):
        pass

    def eat(self):
        pass

    def sleep(self):
        pass

class Human(Worker):
    def work(self):
        print("Human working")

    def eat(self):
        print("Human eating")

    def sleep(self):
        print("Human sleeping")

class Robot(Worker):  # Forced to implement unnecessary methods
    def work(self):
        print("Robot working")

    def eat(self):
        pass  # Robots don't eat - unnecessary method

    def sleep(self):
        pass  # Robots don't sleep - unnecessary method

✅ ISP Better Approach

class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class Sleepable:
    def sleep(self):
        pass

class Human(Workable, Eatable, Sleepable):
    def work(self):
        print("Human working")

    def eat(self):
        print("Human eating")

    def sleep(self):
        print("Human sleeping")

class Robot(Workable):  # Only implements what it needs
    def work(self):
        print("Robot working")

Dependency Inversion Principle (DIP)

Definition

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

Why It Matters

  • Reduces coupling between modules
  • Makes code more flexible and testable
  • Enables dependency injection
  • Supports inversion of control patterns

Examples

❌ Violation Example

class EmailService:
    def send_email(self, message):
        # Direct email sending implementation
        pass

class UserService:
    def __init__(self):
        self.email_service = EmailService()  # Direct dependency

    def register_user(self, user):
        # Registration logic
        self.email_service.send_email("Welcome!")

✅ DIP Better Approach

from abc import ABC, abstractmethod

class NotificationService(ABC):
    @abstractmethod
    def send_notification(self, message):
        pass

class EmailService(NotificationService):
    def send_notification(self, message):
        # Email implementation
        pass

class SMSService(NotificationService):
    def send_notification(self, message):
        # SMS implementation
        pass

class UserService:
    def __init__(self, notification_service: NotificationService):
        self.notification_service = notification_service  # Dependency injection

    def register_user(self, user):
        # Registration logic
        self.notification_service.send_notification("Welcome!")

SOLID Principles Application Flow

flowchart TD
    A[Design New Feature] --> B[Identify Responsibilities]
    B --> C{Single Responsibility?}
    C -->|No| D[Split into Smaller Classes]
    C -->|Yes| E[Define Interfaces]

    D --> E
    E --> F{Open for Extension?}
    F -->|No| G["Use Abstractions and Inheritance"]
    F -->|Yes| H[Check Substitutability]

    G --> H
    H --> I{Liskov Principle OK?}
    I -->|No| J[Redesign Inheritance]
    I -->|Yes| K[Review Interface Size]

    J --> K
    K --> L{Interface Segregation?}
    L -->|No| M[Split Large Interfaces]
    L -->|Yes| N[Check Dependencies]

    M --> N
    N --> O{Dependency Inversion?}
    O -->|No| P[Use Dependency Injection]
    O -->|Yes| Q[Implementation Complete]

    P --> Q

Implementation Guidelines

Code Review Checklist

  • [ ] SRP: Does each class have a single, well-defined responsibility?
  • [ ] OCP: Can new features be added without modifying existing code?
  • [ ] LSP: Are subclasses truly substitutable for their parent classes?
  • [ ] ISP: Are interfaces focused and not forcing unnecessary dependencies?
  • [ ] DIP: Are high-level modules independent of low-level implementation details?

Common Anti-Patterns to Avoid

  • God Classes: Classes that do too many things
  • Rigid Coupling: Direct dependencies on concrete implementations
  • Interface Pollution: Large interfaces with unused methods
  • Violation of Contracts: Subclasses that break parent class expectations
  • Modification Cascade: Changes that require modifying multiple files

Team Standards

  1. Design Review: Apply SOLID principles during architecture discussions
  2. Code Review: Check for SOLID violations in pull requests
  3. Refactoring: Regularly refactor code to improve SOLID compliance
  4. Testing: SOLID code is easier to test - use this as a quality metric
  5. Documentation: Document design decisions and trade-offs

Technology-Specific Implementations

Technology-specific SOLID pattern implementations will be added as the team develops best practices for each stack: - Python/Django SOLID Patterns (coming soon) - TypeScript/React SOLID Patterns (coming soon) - FastAPI SOLID Patterns (coming soon)

References