VOOZH about

URL: https://www.geeksforgeeks.org/system-design/dependency-injectiondi-design-pattern/

⇱ Dependency Injection(DI) Design Pattern - GeeksforGeeks


  • Courses
  • Tutorials
  • Interview Prep

Dependency Injection(DI) Design Pattern

Last Updated : 13 May, 2026

Dependency Injection (DI) is a design pattern where an object receives its dependencies from an external source instead of creating them itself. It separates dependency creation from usage, improving flexibility, testability, and maintainability.

  • Reduces tight coupling between classes while improving code reusability and flexibility.
  • Makes unit testing easier using mock dependencies and enhances system maintainability and scalability.

Example: Car class might depend on a Engine class to run. Without DI, the Car class would directly create or manage the Engine instance within its code, which makes the two classes tightly coupled. This approach can create problems, particularly when you need to test, extend, or modify the classes in the future.

πŸ‘ class_a
Dependency Injection
  • Dependency Injection solves this problem by injecting the dependencies (like the Engine ones in the Car example) into the class from an external source, rather than having the class create them.
  • In simpler terms, DI allows you to "inject" the things a class needs (its dependencies) from the outside, instead of letting the class create or manage them itself.

Four Roles of Dependency Injection

In Dependency Injection, the dependencies of a class are injected from the outside, rather than the class creating or managing its dependencies internally. This pattern has four main roles:

πŸ‘ dependency-injection-di-design-pattern-2
Four Roles of Dependency Injection

1. Client

The client is the class or component that depends on a service to perform its operations.

  • It does not create or manage its dependencies
  • It receives required services from the injector

2. Service

The service is the class or component that provides specific functionality needed by the client.

  • It contains the actual business logic
  • It is designed to be independent of the client

3. Injector

The injector is responsible for creating service instances and supplying them to the client.

  • It manages dependency creation and lifecycle
  • It injects required services at runtime

4. Interface

The interface defines a contract that specifies what methods a service must implement.

  • Clients depend on interfaces, not concrete classes
  • It allows easy replacement of service implementations

Uses

Dependency Injection is widely used to build flexible, maintainable, and loosely coupled applications.

  • Loose Coupling & Reusability: Objects don’t create their own dependencies, making them independent and reusable.
  • Improved Testability: Easily inject mocks or test doubles to test components in isolation.
  • Maintainability & Flexibility: DI frameworks simplify managing and configuring dependencies.
  • Scalability & Extensibility: Helps handle complex dependency graphs in large applications.
  • Cross-Cutting Concerns: Conveniently inject services like logging, security, or caching across multiple components.

Example

Imagine you're building an application that sends notifications to users. You want to make the notification system flexible so you can change the notification provider (email, SMS, push notifications, etc.) without modifying the core application logic.

1. Code Without Dependency Injection:

Issues:

  • Tight Coupling: The NotificationService is tightly coupled to the EmailProvider, making it difficult to switch to a different provider without code changes.
  • Testability: Testing NotificationService in isolation is challenging as it directly uses EmailProvider.

2. Code With Dependency Injection:


Output
Sending Email to abc@example.com: Hello via Email!
Sending SMS to 123-456-7890: Hello via SMS!

Benefits of using Dependency Injection Design Pattern in this solution above:

  • Loose Coupling:NotificationService no longer depends on a specific implementation, making it adaptable to different providers.
  • Testability: You can easily inject mock providers for testing NotificationService in isolation.
  • Maintainability: Code becomes more modular and easier to manage as dependencies are explicit.

Types of Dependency Injection

There are mainly three types of dependency injection, that are Constructor Injection, Setter Injection and Interface Injection. Let's understand these three approaches to dependency injection using an example with the implementation.

You are building a Vehicle Management System for a car rental service. The system needs to manage cars and their engines. Each car should have an engine type and the system should ensure that the car has all necessary components when it's instantiated.

1. Constructor Injection

With Constructor Injection, dependencies are provided to a class through its constructor when the object is created. This is the most common form of DI because it makes dependencies clear, mandatory, and immutable after the object is constructed.


Output
Engine started
Car is driving
  • Engine Class: Has a start() method to simulate starting the engine.
  • Car Class: Receives an Engine via its constructor; drive() uses the injected Engine.
  • Main Method: Creates an Engine instance, injects it into Car through the constructor, then calls car.drive().

2. Setter Injection

Setter Injection involves providing the dependency via a setter method after the object is created. This approach is more flexible than constructor injection because it allows dependencies to be set or changed after object creation.


Output
Engine started
Car is driving
  • Engine Class: Has a start() method to simulate engine behavior.
  • Car Class: Provides setEngine() to inject the Engine; drive() uses the injected Engine to start the car.
  • Main Method: Creates Engine and Car objects, calls setEngine() to inject the Engine, then calls car.drive().

3. Interface Injection

Interface Injection requires the class to implement an interface that provides a method for receiving the dependency. This is less commonly used in Java, but it allows for more flexibility and decoupling.


Output
Engine started
Car is driving
  • Engine Class: Has a start() method to simulate starting the engine.
  • EngineInjector Interface: Declares injectEngine(Engine engine) for injecting an Engine.
  • Car Class: Implements EngineInjector; injectEngine() sets the Engine, drive() calls engine.start().
  • Main Method: Creates Engine and Car objects, injects the Engine into Car, then calls car.drive().

Advantages

Dependency Injection improves flexibility, maintainability, and testability by reducing direct dependencies between components.

  • Increased Modularity & Loose Coupling: Components depend on abstractions instead of concrete implementations, making code cleaner, flexible, and easier to maintain.
  • Improved Testability & Reusability: Dependencies can be replaced with mocks for testing, and components can be reused across different contexts.
  • Easier Collaboration: Teams can work independently on separate components without worrying about internal dependencies.

Limitations

Although Dependency Injection (DI) improves flexibility and maintainability, it also has some limitations in certain scenarios.

  • Increased Complexity: Managing dependencies and configurations can make small applications more complex.
  • Runtime Overhead: Incorrect dependency configuration may lead to runtime errors and slight performance overhead.
  • Difficult Debugging: Testing and troubleshooting dependency-related issues can sometimes be challenging.
Comment
Article Tags:

Explore