| name | SOLID Principles |
| description | Five design principles for writing maintainable, extensible object-oriented software |
| category | software-development |
SOLID Principles
What I do
I provide five fundamental design principles that help software developers create systems that are easy to maintain, understand, and extend. SOLID is an acronym representing Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These principles guide object-oriented design toward more flexible, robust code that can evolve over time without requiring constant rewrites.
When to use me
Apply SOLID principles when designing new classes, refactoring existing code, or reviewing object-oriented architectures. They are especially valuable in large codebases with multiple developers, where consistent design standards prevent chaos. Use them when you need your code to be testable, when requirements change frequently, or when building frameworks/libraries that other code will depend on. Avoid applying them dogatically to trivial code or one-off scripts.
Core Concepts
- S: Single Responsibility - Each class should have one reason to change
- O: Open/Closed - Software should be open for extension, closed for modification
- L: Liskov Substitution - Subtypes must be substitutable for their base types
- I: Interface Segregation - Many specific interfaces are better than one general one
- D: Dependency Inversion - Depend on abstractions, not concretions
- Cohesion: How strongly related a class's responsibilities are
- Coupling: The degree of interdependence between modules
- Abstraction: Representing essential features without implementation details
- Polymorphism: Objects of different types responding to the same interface
- Inversion of Control: Framework controlling program flow, not application
Code Examples
Single Responsibility Principle
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Protocol
class JournalEntry:
def __init__(self, title: str, content: str):
self.title = title
self.content = content
self.created_at = datetime.now()
self.entries: list['JournalEntry'] = []
def add_entry(self, entry: 'JournalEntry') -> None:
self.entries.append(entry)
class Persistence(Protocol):
@abstractmethod
def save(self, filename: str) -> None:
pass
class FilePersistence:
def save(self, journal: JournalEntry, filename: str) -> None:
with open(filename, 'w') as f:
f.write(f"{journal.title}\n{journal.content}")
class :
() -> :
()
:
():
.entries: [JournalEntry] = []
.persister: Persistence | =
() -> JournalEntry:
entry = JournalEntry(title, content)
.entries.append(entry)
entry
() -> :
.persister:
.persister.save(, filename)
Open/Closed Principle
from abc import ABC, abstractmethod
from typing import Protocol
class DiscountStrategy(Protocol):
@abstractmethod
def calculate(self, price: float) -> float:
pass
class RegularDiscount:
def calculate(self, price: float) -> float:
return price * 0.9
class SilverDiscount:
def calculate(self, price: float) -> float:
return price * 0.85
class GoldDiscount:
def calculate(self, price: float) -> float:
return price * 0.8
class PriceCalculator:
def __init__(self, discount: DiscountStrategy):
self.discount = discount
def calculate_final_price(self, price: float) -> :
.discount.calculate(price)
calculator = PriceCalculator(GoldDiscount())
()
Liskov Substitution Principle
from abc import ABC, abstractmethod
from typing import Protocol
class Bird(Protocol):
@abstractmethod
def fly(self) -> None:
pass
@abstractmethod
def eat(self) -> None:
pass
class Sparrow:
def fly(self) -> None:
print("Sparrow flying")
def eat(self) -> None:
print("Sparrow eating")
class Ostrich:
def eat(self) -> None:
print("Ostrich eating")
def run(self) -> None:
print("Ostrich running")
class FlightfulBird(ABC):
@abstractmethod
def fly() -> :
():
() -> :
():
() -> :
()
() -> :
()
() -> :
bird.fly()
make_bird_fly(Eagle())
Interface Segregation Principle
from abc import ABC, abstractmethod
from typing import Protocol
class Printer(Protocol):
@abstractmethod
def print(self, document: str) -> None:
pass
class Scanner(Protocol):
@abstractmethod
def scan(self, document: str) -> None:
pass
class Fax(Protocol):
@abstractmethod
def fax(self, document: str) -> None:
pass
class OldPrinter:
def print(self, document: str) -> None:
print(f"Printing: {document}")
class ModernPrinter:
def print(self, document: str) -> None:
print(f"Printing: ")
() -> :
()
() -> :
()
:
():
.printer = printer
.scanner = scanner
() -> :
.printer.(doc)
() -> :
.scanner.scan(doc)
Dependency Inversion Principle
from abc import ABC, abstractmethod
from typing import Protocol
class MessageSender(Protocol):
@abstractmethod
def send(self, message: str, recipient: str) -> bool:
pass
class EmailSender:
def send(self, message: str, recipient: str) -> bool:
print(f"Sending email to {recipient}: {message}")
return True
class SMSender:
def send(self, message: str, recipient: str) -> bool:
print(f"Sending SMS to {recipient}: {message}")
return True
class NotificationService:
def __init__(self, sender: MessageSender):
self.sender = sender
def send_notification(self, message: , recipient: ) -> :
.sender.send(message, recipient)
:
():
.name = name
.email = email
.phone = phone
email_service = EmailSender()
notification = NotificationService(email_service)
notification.send_notification(, )
Best Practices
- Aim for High Cohesion: Each class should have tightly related responsibilities
- Reduce Coupling: Minimize dependencies between components
- Design to Interfaces: Define contracts before implementations
- Composition over Inheritance: Prefer flexible object composition
- Inject Dependencies: Pass dependencies rather than hard-coding them
- Refactor Regularly: Apply SOLID principles during code reviews
- Testability: SOLID code is inherently more testable
- Avoid God Classes: Split large classes into focused components
- Abstract Frameworks: Depend on abstractions, not concrete implementations
- YAGNI: Don't over-engineer; apply principles pragmatically