Dependency Injection
Hand a class its collaborators from the outside (constructor, setter, or a framework) instead of letting it construct them itself, so it depends on an interface without knowing how to build what implements it.
The problem
OrderService needs to charge a card, so it builds a StripeGateway in its own constructor
and calls it a day. Simple, until two things happen: a test tries to run checkout() and
ends up needing a real Stripe API key, and the business decides to add PayPal as a second
provider next quarter.
The dependency is not the problem - OrderService obviously needs some way to charge
cards. The problem is who decides which one, and when. Baking new StripeGateway() into
the constructor answers that question at compile time, permanently, for every caller
everywhere.
The solution
OrderService still depends on a PaymentGateway interface - that part does not change.
What changes is who builds the concrete implementation: not OrderService, but whoever
assembles the program. OrderService's constructor simply asks for a PaymentGateway and
uses whatever arrives.
Production code hands it a StripeGateway wired to a real API key. Test code hands it a
SandboxGateway that always succeeds instantly. OrderService never changes, never
recompiles differently, and never contains the word "Stripe".
- 1Startup code builds the real gateway - the one place in the whole program that knows Stripe exists.
- 2The gateway is handed to OrderService through its constructor. OrderService never called `new` on it.
- 3Checkout logic calls the interface method it was given. It could be Stripe, PayPal, or a mock - the call site is identical.
- 4Whatever the real gateway does to actually move money happens entirely behind the interface.
- 5A test builds a stand-in gateway that always "succeeds" with no network call.
- 6The exact same constructor, a different argument. OrderService's source code does not change at all.
- 7Same call as production. This time it resolves instantly and deterministically, with zero network flakiness.
Structure
The diagram has no line at all connecting OrderService to either concrete gateway - only
to the interface. That missing line is the entire pattern.
Code
Same example three ways: a checkout flow that receives its payment gateway instead of building one.
When to use it
- A class currently constructs its own collaborators (
new SomeService()inside a constructor or method) and that collaborator is exactly the thing a test would want to replace. - More than one implementation of a dependency is expected to exist, now or eventually (multiple payment providers, multiple storage backends, a mock for tests).
Pitfalls
- Parameter explosion as a disguise. Injecting ten dependencies into one constructor
does not fix a class doing ten jobs - it just makes the ten jobs visible in the parameter
list instead of buried in
newcalls scattered through the method bodies. - Injecting concretions instead of interfaces.
OrderService(StripeGateway gateway)still couples to Stripe; the parameter type has to be the interface, or nothing was actually decoupled. - Container magic replacing understanding. A DI framework wiring dozens of beans by convention can make it genuinely hard to trace, by reading, which concrete class ends up behind an interface at runtime. That is a real cost, not just a learning curve.
Don't confuse it with
- Factory Method. Factory Method still creates the object, inside a method the consumer calls; the consumer just delegates the "which concrete class" decision to a subclass. DI removes creation from the consumer's code entirely - it receives a finished object and never calls a constructor for it at all.
- Service Locator. A Service Locator is a global registry a class actively queries for
its dependencies (
Locator.get(PaymentGateway.class)) - the class still reaches out and asks. DI hands the dependency in without the class ever asking; the class is passive. - Strategy. The mechanics can be identical code, but Strategy names the pattern of swapping an interchangeable algorithm at runtime for behavioral reasons; DI names how any collaborator, algorithmic or not, gets into a class in the first place.
Check yourself
What does OrderService know about how to construct a PaymentGateway?