AlgoMaster Logo

Dependency

Ashish

Ashish Pratap Singh

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.

1. What is Dependency?

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:

  • A class accepts another class as a method parameter.
  • A class instantiates or uses another class inside a method.
  • A class returns an object of another class from a method.

If class A uses class B just to perform a task, not to store or own, that’s a dependency.

Key Characteristics of Dependency

  • Short-lived: The relationship exists only during method execution.
  • No ownership: The dependent class does not store the other as a field.
  • "Uses-a" relationship: The class uses another to accomplish a task, but does not retain it.

2. UML Representation

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.

3. Code Example

Let’s model a simple Printer that depends on a Document to print.

Document Class

Printer Class

Here:

  • Printer depends on Document to complete its work.
  • But Printer does not store or own the Document, it simply uses it during method execution.

4. Recognizing Dependencies in Code

Dependencies can appear in several common forms within a class:

As Method Parameters

The dependency is passed into a method only when needed.

As Class Fields/Instance Variables

The dependency is held as a field, often for repeated use.

As Constructor Parameters

The dependency is provided when the object is created. This is the foundation for Dependency Injection and is highly preferred.

5. Dependency Injection (DI)

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:

  • Better testability: You can inject mock dependencies during unit tests.
  • Greater modularity: Swap implementations (e.g., EmailSenderSMSSender) without changing core logic.
  • Loose coupling: Classes only depend on abstract contracts (interfaces), not concrete implementations.

Example

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.

  • The dependency (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.