| name | solid-principles |
| description | Apply SOLID principles to write maintainable, extensible object-oriented code. Outputs principle-by-principle analysis, refactoring examples, and design decision guidelines. |
| argument-hint | ["codebase language","identified violations","team OOP experience level"] |
| allowed-tools | Read, Write |
SOLID Principles
SOLID is five design principles that make object-oriented code easier to understand, extend, and maintain. Each principle targets a specific category of design rot: rigidity, fragility, immobility, viscosity, and needless complexity.
S — Single Responsibility Principle
A class should have one reason to change. One responsibility, one axis of change.
class UserService:
def create_user(self, data): ...
def send_welcome_email(self, user): ...
def generate_report(self, users): ...
def hash_password(self, password): ...
def export_to_csv(self, users): ...
class UserService:
def __init__(self, user_repo, email_service):
self._repo = user_repo
self._email = email_service
def create(self, data: dict) -> User:
user = User(**data)
self._repo.save(user)
self._email.send_welcome(user)
return user
class EmailService:
def send_welcome(self, user: User): ...
class UserReportGenerator:
def generate(self, users: list[User]) -> Report: ...
class PasswordHasher:
def hash(self, plain: str) -> str: ...
O — Open/Closed Principle
Open for extension, closed for modification. Add new behaviour without changing existing code.
class OrderPricer:
def calculate(self, order, discount_type: str) -> float:
if discount_type == "percentage":
return order.total * 0.9
elif discount_type == "fixed":
return order.total - 10
elif discount_type == "bogo":
return order.total * 0.5
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, total: float) -> float: ...
class PercentageDiscount(DiscountStrategy):
def __init__(self, pct: float): self._pct = pct
def apply(self, total: float) -> float: return total * (1 - self._pct)
():
(): ._amount = amount
() -> : total - ._amount
():
() -> : total *
:
() -> :
discount.apply(order.total)
L — Liskov Substitution Principle
Subtypes must be substitutable for their base types without altering program correctness.
class Rectangle:
def set_width(self, w): self.width = w
def set_height(self, h): self.height = h
def area(self) -> float: return self.width * self.height
class Square(Rectangle):
def set_width(self, w):
self.width = w
self.height = w
def resize(rect: Rectangle):
rect.set_width(5)
rect.set_height(10)
assert rect.area() == 50
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Rectangle(Shape):
(): .width = w; .height = h
(): .width * .height
():
(): .side = side
(): .side **
I — Interface Segregation Principle
Clients should not depend on interfaces they don't use. Prefer small, focused interfaces.
class Worker(ABC):
@abstractmethod
def work(self): ...
@abstractmethod
def eat(self): ...
@abstractmethod
def sleep(self): ...
class Robot(Worker):
def work(self): ...
def eat(self): raise NotImplementedError
def sleep(self): raise NotImplementedError
class Workable(ABC):
@abstractmethod
def work(self): ...
class Eatable(ABC):
@abstractmethod
def eat(self): ...
class HumanWorker(Workable, Eatable):
(): ...
(): ...
():
(): ...
D — Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.
class OrderProcessor:
def __init__(self):
self._repo = MySQLOrderRepository()
def process(self, order_id: str):
order = self._repo.get(order_id)
class OrderRepository(ABC):
@abstractmethod
def get(self, order_id: str) -> Order: ...
@abstractmethod
def save(self, order: Order): ...
class OrderProcessor:
def __init__(self, repo: OrderRepository):
self._repo = repo
def process(self, order_id: str):
order = self._repo.get(order_id)
class MySQLOrderRepository(OrderRepository):
def get(self, order_id): ...
():
(): ...
processor = OrderProcessor(MySQLOrderRepository())
processor = OrderProcessor(InMemoryOrderRepository())
Anti-Patterns to Avoid
| Principle | Violation | Fix |
|---|
| SRP | God class with 20+ methods | Extract into focused collaborators |
| OCP | Long if/elif chains for type dispatch | Strategy or visitor pattern |
| LSP | Subclass throws NotImplementedError | Restructure hierarchy; use composition |
| ISP | Interface with 10+ methods | Split into role-specific interfaces |
| DIP | new ConcreteClass() inside business logic | Constructor injection; depend on ABC |
10 Rules
- A class that needs to import from 5+ modules to function is violating SRP.
- If adding a feature requires editing existing code (not just adding new code), OCP is being violated.
- If a unit test needs to set up 10 things to test one thing, DIP is probably violated.
- LSP: if you ever check
isinstance(x, SubClass) in the base class logic, LSP is broken.
- ISP: interfaces with "does not apply" methods in implementations need splitting.
- DIP makes testing possible — you can inject test doubles without monkeypatching.
- SOLID is a spectrum, not a binary — apply judgment; over-engineering is also a failure mode.
- Apply OCP to the most likely change axes — not every possible extension.
- Small interfaces (2-3 methods) are almost always better than large ones.
- SOLID principles work together — DIP without ISP leads to bloated injected interfaces.