Dependency Inversion: depend on a contract
Keep business policy pointed at an abstraction; let replaceable infrastructure implement that abstraction.
Watch it animatedStep through it, with the running line highlighted. Free with an account.The idea
OrderService is high-level policy; EmailNotifier is an infrastructure detail. Both meet at INotifier, so the policy does not need to construct or know the concrete email implementation. Constructor injection supplies the chosen implementation.
Code
class Notifier {
public:
virtual void send(const string& message) = 0;
virtual ~Notifier() = default;
};
class EmailNotifier : public Notifier {
public:
void send(const string& message) override {
cout << "Email: " << message << "\n";
}
};
class OrderService {
Notifier& notifier;
public:
OrderService(Notifier& notifier)
: notifier(notifier) {}
void ship() {
notifier.send("Order shipped");
}
};
int main() {
EmailNotifier email;
OrderService service(email);
service.ship();
}import asyncio
from typing import Protocol
class Notifier(Protocol):
async def send(self, message: str) -> None: ...
class EmailNotifier:
async def send(self, message: str) -> None:
print(f"Email: {message}")
class OrderService:
def __init__(self, notifier: Notifier):
self.notifier = notifier
async def ship(self) -> None:
await self.notifier.send("Order shipped")
service = OrderService(EmailNotifier())
asyncio.run(service.ship())interface INotifier {
Task SendAsync(string message);
}
class EmailNotifier : INotifier {
public Task SendAsync(string message) {
Console.WriteLine($"Email: {message}");
return Task.CompletedTask;
}
}
class OrderService {
private readonly INotifier notifier;
public OrderService(INotifier notifier) {
this.notifier = notifier;
}
public Task ShipAsync() =>
notifier.SendAsync("Order shipped");
}
var service = new OrderService(new EmailNotifier());
await service.ShipAsync();Tips
- Dependency Inversion is the design principle. Dependency Injection is a technique for supplying the dependency; the terms are related but not interchangeable.
- Use the smallest useful abstraction. An interface is valuable when it separates a policy from a detail or gives tests a meaningful seam.
In the .NET library
At runtime OrderService still calls the notifier. The inversion is in the source dependency: the concrete notifier implements the contract that the high-level service consumes.