This document outlines the SOLID principles with examples relevant to the Accellens codebase. All backend and frontend code should strive to adhere to these principles to maintain scalability and testability.
A class or module should have one, and only one, reason to change.
class ReportGenerator:
def generate_report(self, data):
# Format data
report = f"Title: {data['title']}..."
# Save to file
with open('report.txt', 'w') as f:
f.write(report)
# Email report
smtp.send_message(report)Reason to change: Logic changes for formatting, saving, OR emailing.
class ReportFormatter:
def format(self, data) -> str: ...
class ReportSaver:
def save(self, report: str, path: str): ...
class EmailService:
def send(self, content: str): ...Software entities should be open for extension, but closed for modification.
def calculate_area(shape):
if isinstance(shape, Rectangle):
return shape.width * shape.height
elif isinstance(shape, Circle):
return 3.14 * shape.radius ** 2Issue: Adding a new shape requires modifying calculate_area.
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Rectangle(Shape):
def area(self) -> float:
return self.width * self.height
class Circle(Shape):
def area(self) -> float:
return math.pi * self.radius ** 2Subtypes must be substitutable for their base types.
class Bird:
def fly(self): ...
class Ostrich(Bird):
def fly(self):
raise NotImplementedError("Ostriches can't fly")Issue: Ostrich breaks the contract of Bird.
class Bird:
def move(self): ...
class FlyingBird(Bird):
def fly(self): ...
class Ostrich(Bird):
def move(self):
self.run()Clients should not be forced to depend on methods they do not use.
interface Worker {
work(): void;
eat(): void;
}
class Robot implements Worker {
work() { ... }
eat() { throw new Error("Robots don't eat"); }
}interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
class Robot implements Workable { ... }
class Human implements Workable, Eatable { ... }High-level modules should not depend on low-level modules. Both should depend on abstractions.
class UserService:
def __init__(self):
self.db = PostgresDatabase() # Hard dependencyclass DatabaseInterface(ABC):
@abstractmethod
def save(self, data): ...
class UserService:
def __init__(self, db: DatabaseInterface):
self.db = db # Dependency injection
class PostgresDatabase(DatabaseInterface): ...