Dependency inversion
coreintermediateDependency 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.
Think of it as
A concrete dependency is a class hardwired to one specific brand of plug. An abstraction is the wall socket standard — anything that fits the socket works, real appliance or test rig, without rewiring the wall. OrderService should depend on the socket shape (Mailer), never on one specific plug (SmtpMailer).
What we're doing: Compare a class hardwired to one concrete mailer against a class that depends on a Mailer abstraction instead.
- 7
- OrderService builds SmtpMailer itself — the concrete class is baked into the constructor.
- 20
- Mailer is an abstraction: any class with a matching send(to, subject) satisfies it.
- 24
- OrderService2 depends on Mailer, not SmtpMailer2 — the caller decides which concrete class to pass in.
SMTP: sent 'Order confirmed' to a@example.com
SMTP: sent 'Order confirmed' to a@example.comWhy this works: Both versions print the same line, which is the point — inverting the dependency changes who is allowed to choose the concrete class, not what the code does today. OrderService can only ever use SmtpMailer; OrderService2 can accept SmtpMailer2, a different mailer, or a test fake, because it only requires the Mailer shape.
Depending on a concrete class and calling it "flexible" because it has a constructor argument
Wrong
Better
What you see: A test double with the wrong methods still passes type-checks, because the parameter is typed as the concrete class, not the abstraction it should satisfy — the coupling is only hidden, not removed.
Why: Typing a parameter as SmtpMailer (even with a default) still names the concrete class, so every caller and every type-checker is reasoning about SmtpMailer specifically. Typing it as Mailer states the actual contract — send(to, subject) — and any class satisfying it is a legal argument, which is what makes swapping implementations safe.
- 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
Remember: Depend 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.
See also: constructor injection · testing with injected dependencies · abc module

