Dependency injection pattern
Invert the dependency direction by injecting collaborators from outside rather than creating them internally, enabling testability and loose coupling.
Introduction
Dependency Injection (DI) separates using a collaborator from creating it. A class declares the collaborators it needs, and code outside the class supplies concrete implementations. The result is an explicit dependency boundary that is easier to configure, test, and change.
TL;DR / mental model
Think of DI as βpush dependencies in.β The composition root creates the concrete objects and passes them into a service; the service depends on interfaces and focuses on its own behavior. Constructor injection is the usual default for required collaborators.
The Problem It Solves
You're building an order service. It needs to charge the customer, send a confirmation email, and store the order. So you new up a StripeGateway, a SmtpMailer, and a MySqlOrderRepository right inside the constructor.
Three problems compound here. First, you cannot test placeOrder() without calling Stripe and a real database. Second, swapping any implementation (say, Stripe to PayPal) means cracking open OrderService and modifying it. Third, every class that uses OrderService transitively depends on Stripe, SMTP, and MySQL.
The root cause is that OrderService both uses its collaborators and creates them. Dependency Injection separates those two responsibilities.
Here is what changes when you apply Dependency Injection.
Structure
OrderService declares what it needs through interfaces in its constructor. It never decides which concrete class fulfills those needs. The Injector (also called the composition root) wires everything together at startup. In a test, you swap StripeGateway for FakeGateway without touching OrderService at all.
Participants
- Consumer: the service that declares and uses collaborators.
- Dependency contract: an interface or abstraction that describes what the consumer needs.
- Implementation: a production, test, or environment-specific class that fulfills the contract.
- Composition root: the startup or configuration boundary that selects implementations and assembles the object graph.
- Client or framework: starts the composition process and hands the assembled consumer to the rest of the application.
Implementation
DI can be shown in three layers: the interfaces, the service that consumes them, and the injector that wires everything. This is manual or βpureβ DI; no framework is required.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.
Related Articles
The factory method replaces direct constructor calls with a method that subclasses can override to return different types. Callers depend on an abstraction, not a concrete class.
The strategy pattern extracts a family of algorithms behind an interface so the client can swap behaviors at runtime without touching the context class.
The adapter pattern wraps an incompatible third-party interface so your code can use it through an interface it already expects. Structural bridging without changing either side.