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
- Design Review: Apply SOLID principles during architecture discussions
- Code Review: Check for SOLID violations in pull requests
- Refactoring: Regularly refactor code to improve SOLID compliance
- Testing: SOLID code is easier to test - use this as a quality metric
- 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)