Dependency Injection
In short
Dependency injection is a design technique in which an object receives the other objects it needs from the outside instead of creating them itself.
What is dependency injection?
A dependency is any object or service that a piece of code needs to do its job, such as a database connection, a logger, or an email client. With dependency injection, a class doesn't build its own dependencies with new; they are passed in from the outside, most often through the constructor. The class only states what it needs, and other code decides which concrete implementation to provide.
There are three common styles: constructor injection, where dependencies are passed in when the object is created; setter or property injection, where they are assigned afterward; and parameter injection, where they are passed into a single function call. In larger applications, this wiring is often handled by a DI container, a framework component that creates objects and supplies their dependencies automatically.
The biggest payoff is testability and flexibility. Because a service receives its database or payment client from outside, a test can pass in a fake version that returns predictable data, and production code can switch email providers without editing the service. A useful analogy is a lamp with a plug: the lamp doesn't generate its own electricity, it simply works with whatever power socket it is connected to.
Dependency injection is often confused with the Dependency Inversion Principle, the D in SOLID. Dependency inversion is the design rule that code should depend on abstractions such as interfaces, while dependency injection is a practical technique for supplying those abstractions. Inversion of control is the broader idea that a framework or caller, rather than your own code, controls how objects are created and connected, and DI is one way to achieve it.
Key takeaways
- Dependency injection passes a class the objects it needs instead of letting it create them.
- Constructor injection is the most common and most explicit style.
- It makes code easier to test, because real dependencies can be swapped for fakes.
- DI containers automate the wiring in larger applications.
- DI is a technique for applying the Dependency Inversion Principle.
Example
interface Mailer {
send(to: string, text: string): Promise<void>;
}
class SignupService {
// The dependency is injected through the constructor
constructor(private mailer: Mailer) {}
async register(email: string) {
await this.mailer.send(email, "Welcome aboard!");
}
}
// Production passes a real mailer; a test can inject a fake one
const fakeMailer: Mailer = { send: async () => {} };
new SignupService(fakeMailer).register("ada@example.com");Readers ask
What is the difference between dependency injection and dependency inversion?
Dependency inversion is a design principle stating that code should depend on abstractions rather than concrete classes. Dependency injection is a technique for supplying dependencies from the outside, and it is the most common way to follow that principle.
Do I need a framework for dependency injection?
No. Passing dependencies through constructors or function parameters is dependency injection, and it works in any language. DI containers are optional tools that automate the wiring when an application has many services.
Why does dependency injection make testing easier?
Because the code under test receives its dependencies from outside, a test can pass in fakes or mocks that return predictable results. This lets you test logic without a real database, network, or email server.
See also
- SOLIDSoftware Architecture, p. 42SOLID is a set of five object-oriented design principles that help developers write code that is easier to understand, extend, test, and maintain.
- Design PatternSoftware Architecture, p. 13A design pattern is a proven, reusable solution to a common problem in software design, described as a general template rather than as finished code.
- OOPProgramming Fundamentals, p. 40OOP, or object-oriented programming, is a way of structuring code around objects that bundle related data together with the functions that act on that data.
- ClassProgramming Fundamentals, p. 9A class is a blueprint in object-oriented programming that defines the data and behavior shared by a group of objects, which are created from it as instances.
- Separation of ConcernsSoftware Architecture, p. 37Separation of concerns is a design principle that divides a program into distinct parts, each responsible for one clearly defined aspect of its behavior.
- Clean ArchitectureSoftware Architecture, p. 5Clean architecture is a way of structuring software in layers so that the core business rules never depend on frameworks, databases, or user interfaces.
Spotted a mistake or something missing on this page?Suggest an edit