What happens when a class needs to use another class for a brief moment to get a job done, without needing to hold onto it forever?
This is the world of Dependency.
Unlike association, aggregation, or composition, dependency is not structural. It does not imply a long-term relationship or shared lifecycle. Instead, it reflects a one-time interaction, often through method parameters, local variables, or return types.
A Dependency exists when one class relies on another to fulfill a responsibility, but does so without retaining a permanent reference to it.
This typically happens when:
If class A uses class B just to perform a task, not to store or own, that’s a dependency.
Imagine a Chef preparing a meal.
This represents a dependency. The chef depends on the knife only during the cooking process.
In UML class diagrams, dependency is shown using a dashed arrow pointing from the dependent class to the class it depends on.
This indicates that Printer temporarily uses Document, but does not own or associate with it in a structural sense.
Let’s model a simple Printer that depends on a Document to print.
Document ClassPrinter ClassHere:
Printer depends on Document to complete its work.Printer does not store or own the Document, it simply uses it during method execution.Dependencies can appear in several common forms within a class:
The dependency is passed into a method only when needed.
The dependency is held as a field, often for repeated use.
The dependency is provided when the object is created. This is the foundation for Dependency Injection and is highly preferred.
In real-world applications, classes often depend on other classes to get their work done.
A UserService might rely on a DatabaseClient to fetch users, or a NotificationService might rely on an EmailSender to send messages.
But how should these dependencies be provided?
You could let the class create its own dependencies internally but that leads to tight coupling, making your code rigid and hard to test.
A better approach is to inject those dependencies from the outside.
This is called Dependency Injection (DI), one of the most powerful principles in modern software design.
Dependency Injection is a design technique where a class receives the objects it depends on, instead of creating them itself.
This leads to:
EmailSender → SMSSender) without changing core logic.Let’s take a look at a simple Notification system. Instead of creating a Sender object inside the class, we inject it through the constructor.
Sender) is provided externally, not created internally.NotificationService does not care how messages are sent, it just depends on a Sender interface.This promotes loose coupling, testability, and open/closed design.
In real-world applications, frameworks like Spring (for Java) handle DI for you. They automatically resolve and inject dependencies based on configuration or annotations so you don’t have to wire everything manually.