1.Dependency inversion
Dependency inversion means a class depends on an abstraction (like an ABC with a send method) instead of one concrete class it builds itself. That abstraction is what makes swapping the real implementation for a test double possible.
Key features
- High-level code should depend on an abstraction, not a concrete class it instantiates itself
- In Python, an abc.ABC with @abstractmethod is a common way to express that abstraction explicitly
- Duck typing means the abstraction doesn't even need a base class — any object with the right method works
- Dependency injection is the technique; dependency inversion is the principle it exists to satisfy
- The concrete class still gets built somewhere — inversion moves that decision out of the class that uses it
- Tightly coupled
- OrderService creates SmtpMailer itself
- Swapping mailers means editing OrderService
- A test cannot avoid sending real email
- Depends on an abstraction
- OrderService accepts anything shaped like Mailer
- SmtpMailer, a queue-based mailer, or a fake all fit
- A test passes a fake with no code changes
Command
from abc import ABC, abstractmethod
class Mailer(ABC):
@abstractmethod
def send(self, to, subject): ...
class OrderService:
def __init__(self, mailer: Mailer): # depends on the abstraction
self.mailer = mailerDepend on the abstraction (what a dependency must do), not the concrete class (how one particular version does it) — that is what makes swapping in a test double possible.