Dependency Injection Pattern
Dependency Injection: separate a useful responsibility
See the responsibility boundary
An order service creates its own email client, making it hard to test without contacting a real provider. The service needs a capability, but should not secretly own every detail of choosing and constructing it.
This application-pattern lesson connects local organization with the system contracts discussed elsewhere in the course. The code is an original small local example, not a production persistence or web framework.
What you’ll learn
- Explain the responsibility Dependency Injection separates.
- Follow its local example and identify the implementation boundary.
- Compare related patterns and test the required guarantees.
How the responsibility is divided
Dependency Injection supplies dependencies to a consumer from an explicit assembly boundary. The consumer states what contract it needs; the composition root chooses and connects implementations. Constructor, function-parameter and framework-supported injection are common forms.
The example supplies a notifier to the order service. A test can use a recording implementation without changing the order behaviour. A container is optional; explicit function arguments can implement the responsibility.
Follow a small JavaScript example
Read the program and compare the expected output. Its state is local to one execution context; a real implementation needs the persistence, request and effect contracts described in the trade-offs.
JavaScript · local teaching example
function orderService(notifier) {
return {ready: id => notifier.send('Order ' + id + ' ready')};
}
const recordingNotifier = {send: message => 'recorded: ' + message};
console.log(orderService(recordingNotifier).ready('O1'));Expected output
recorded: Order O1 readyChoose a boundary from the situation
Compare boundary choices
Compare the visible consequences. With JavaScript enabled, choose an option in each scenario to assemble your decision record.
This is an exploratory design exercise. It does not run infrastructure, measure capacity or replace the lesson’s quiz.
All alternatives and consequences remain visible. Select one per situation to record your reasoning.
Choose it for a concrete need
Use it when dependency choices or test boundaries need to be explicit. Keep the composition root understandable and define request, process or shared lifetimes deliberately.
An oversized dependency container can hide wiring errors and make failures appear late. A Service Locator asks a global resolver for dependencies, which can obscure what a consumer requires. Injecting an object does not make its effects atomic or safe for every concurrent caller.
Compare the neighbouring responsibilities
Strategy supplies interchangeable behaviour; injection is one way to provide that strategy. A Singleton describes a scoped lifetime and can be injected without giving consumers global access.
Continue with Strategy’s interchangeable contract, shared lifetime and global access, construction extension points.
For primary further reading, use Martin Fowler’s Dependency Injection explanation and compare the responsibilities with this course’s original example.
Check your pattern reasoning
Check your pattern reasoning
Keep the boundary and its guarantees explicit
Use Dependency Injection when its responsibility matches the actual problem. Keep implementation lifetime, concurrency and error behaviour visible rather than treating an organizational label as proof.
Return to the design-pattern guide or apply this alongside construction extension points.
Reaching the recap and completing the quizzes are separate milestones.