SOLID Principles — Five Rules for Extensible Object-Oriented Design
Five design principles that keep code open to extension without modification.
When to use
- Designing classes/modules in any OO codebase
- When adding features keeps breaking existing code
- When tests require extensive mocking to isolate units
Tradeoffs
- DIP adds indirection (more interfaces); overhead for simple scripts
- Over-applying SRP creates class explosion
- Go
- Python
// SRP: one responsibility per type
type InvoicePrinter struct{}
func (p InvoicePrinter) Print(i Invoice) {}
// OCP: extend via interface, not modification
type Discounter interface{ Apply(price float64) float64 }
// LSP: subtypes must honour the contract
type ReadOnlyRepo interface{ Find(id int) Item }
// ISP: small, focused interfaces
type Writer interface{ Write([]byte) error }
// DIP: depend on abstraction
type OrderService struct{ repo OrderRepo }
func NewOrderService(r OrderRepo) *OrderService { return &OrderService{r} }
# SRP
class InvoicePrinter:
def print(self, invoice): ...
# OCP
class Discounter(ABC):
@abstractmethod
def apply(self, price: float) -> float: ...
# LSP
class ReadOnlyRepo(ABC):
@abstractmethod
def find(self, id: int) -> Item: ...
# ISP
class Writer(ABC):
@abstractmethod
def write(self, data: bytes) -> None: ...
# DIP
class OrderService:
def __init__(self, repo: OrderRepo) -> None:
self._repo = repo
Gotcha: LSP violation is often a sign you need composition, not inheritance.