Dependency Injection(DI) Design Pattern (original) (raw)

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.

**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

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.

2. Service

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

3. Injector

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

4. Interface

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

Uses

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

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:

C++ `

#include #include "EmailProvider.h"

class NotificationService { private: EmailProvider emailProvider;

public: void sendNotification(const std::string& message, const std::string& recipient) { emailProvider.sendEmail(message, recipient); } };

Java

public class NotificationService { private EmailProvider emailProvider = new EmailProvider(); // Tightly coupled to email

public void sendNotification(String message, String recipient) {
    emailProvider.sendEmail(message, recipient);
}

}

Python

from EmailProvider import EmailProvider

class NotificationService: def init(self): self.email_provider = EmailProvider()

def send_notification(self, message, recipient):
    self.email_provider.send_email(message, recipient)

JavaScript

class EmailProvider { sendEmail(message, recipient) { // Implementation for sending email } }

class NotificationService { constructor() { this.emailProvider = new EmailProvider(); }

sendNotification(message, recipient) {
    this.emailProvider.sendEmail(message, recipient);
}

}

`

**Issues:

2. Code With Dependency Injection:

C++ `

// Interface for different notification providers #include #include

class NotificationProvider { public: virtual void sendNotification(const std::string& message, const std::string& recipient) = 0; };

// Concrete implementation: Email class EmailProvider : public NotificationProvider { public: void sendNotification(const std::string& message, const std::string& recipient) override { std::cout << "Sending Email to " << recipient << ": " << message << std::endl; } };

// Concrete implementation: SMS class SMSProvider : public NotificationProvider { public: void sendNotification(const std::string& message, const std::string& recipient) override { std::cout << "Sending SMS to " << recipient << ": " << message << std::endl; } };

// Notification service that uses dependency injection class NotificationService { private: NotificationProvider* notificationProvider; public: NotificationService(NotificationProvider* notificationProvider) : notificationProvider(notificationProvider) {} void sendNotification(const std::string& message, const std::string& recipient) { notificationProvider->sendNotification(message, recipient); } };

// Main function int main() { // Inject EmailProvider EmailProvider emailProvider; NotificationService emailService(&emailProvider); emailService.sendNotification("Hello via Email!", "abc@example.com");

// Inject SMSProvider
SMSProvider smsProvider;
NotificationService smsService(&smsProvider);
smsService.sendNotification("Hello via SMS!", "123-456-7890");
return 0;

}

Java

// Interface for different notification providers interface NotificationProvider { void sendNotification(String message, String recipient); }

// Concrete implementation: Email class EmailProvider implements NotificationProvider { @Override public void sendNotification(String message, String recipient) { System.out.println("Sending Email to " + recipient + ": " + message); } }

// Concrete implementation: SMS class SMSProvider implements NotificationProvider { @Override public void sendNotification(String message, String recipient) { System.out.println("Sending SMS to " + recipient + ": " + message); } }

// Notification service that uses dependency injection class NotificationService { private NotificationProvider notificationProvider;

// Dependency injected via constructor
public NotificationService(NotificationProvider notificationProvider) {
    this.notificationProvider = notificationProvider;
}

public void sendNotification(String message, String recipient) {
    notificationProvider.sendNotification(message, recipient);
}

}

// Main class public class GFG { public static void main(String[] args) { // Inject EmailProvider NotificationProvider emailProvider = new EmailProvider(); NotificationService emailService = new NotificationService(emailProvider); emailService.sendNotification("Hello via Email!", "abc@example.com");

    // Inject SMSProvider
    NotificationProvider smsProvider = new SMSProvider();
    NotificationService smsService = new NotificationService(smsProvider);
    smsService.sendNotification("Hello via SMS!", "123-456-7890");
}

}

Python

Interface for different notification providers

from abc import ABC, abstractmethod

class NotificationProvider(ABC): @abstractmethod def sendNotification(self, message: str, recipient: str): pass

Concrete implementation: Email

class EmailProvider(NotificationProvider): def sendNotification(self, message: str, recipient: str): print(f"Sending Email to {recipient}: {message}")

Concrete implementation: SMS

class SMSProvider(NotificationProvider): def sendNotification(self, message: str, recipient: str): print(f"Sending SMS to {recipient}: {message}")

Notification service that uses dependency injection

class NotificationService: def init(self, notificationProvider: NotificationProvider): self.notificationProvider = notificationProvider

def sendNotification(self, message: str, recipient: str):
    self.notificationProvider.sendNotification(message, recipient)

Main function

if name == "main": # Inject EmailProvider emailProvider = EmailProvider() emailService = NotificationService(emailProvider) emailService.sendNotification("Hello via Email!", "abc@example.com")

# Inject SMSProvider
smsProvider = SMSProvider()
smsService = NotificationService(smsProvider)
smsService.sendNotification("Hello via SMS!", "123-456-7890")

` JavaScript ``

// Interface for different notification providers // Concrete implementation: Email class EmailProvider { sendNotification(message, recipient) { console.log(Sending Email to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>c</mi><mi>i</mi><mi>p</mi><mi>i</mi><mi>e</mi><mi>n</mi><mi>t</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{recipient}: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">rec</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">i</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{message}); } }

// Concrete implementation: SMS class SMSProvider { sendNotification(message, recipient) { console.log(Sending SMS to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>c</mi><mi>i</mi><mi>p</mi><mi>i</mi><mi>e</mi><mi>n</mi><mi>t</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{recipient}: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">rec</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">i</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{message}); } }

// Notification service that uses dependency injection class NotificationService { constructor(notificationProvider) { this.notificationProvider = notificationProvider; }

sendNotification(message, recipient) {
    this.notificationProvider.sendNotification(message, recipient);
}

}

// Main function (() => { // Inject EmailProvider const emailProvider = new EmailProvider(); const emailService = new NotificationService(emailProvider); emailService.sendNotification("Hello via Email!", "abc@example.com");

// Inject SMSProvider
const smsProvider = new SMSProvider();
const smsService = new NotificationService(smsProvider);
smsService.sendNotification("Hello via SMS!", "123-456-7890");

})();

``

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:

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.

C++ `

#include

class Engine { public: void start() { std::cout << "Engine started" << std::endl; } };

class Car { private: Engine engine; // Declaring a dependency on Engine

public: // Constructor Injection: Dependency is provided through the constructor Car(Engine engine) : engine(engine) {} // Engine dependency is injected via constructor

void drive() {
    engine.start();  // Using the injected Engine dependency
    std::cout << "Car is driving" << std::endl;
}

};

int main() { Engine engine; // Create Engine object (dependency)

// Injecting Engine dependency when creating Car
Car car(engine);  // Pass the Engine instance to the constructor
car.drive();  // Call the drive method to use the Engine
return 0;

}

Java

class Engine { public void start() { System.out.println("Engine started"); } }

class Car { private Engine engine; // Declaring a dependency on Engine

// Constructor Injection: Dependency is provided through the constructor
public Car(Engine engine) {
    this.engine = engine;  // Engine dependency is injected via constructor
}

public void drive() {
    engine.start();  // Using the injected Engine dependency
    System.out.println("Car is driving");
}

}

public class Main { public static void main(String[] args) { Engine engine = new Engine(); // Create Engine object (dependency)

    // Injecting Engine dependency when creating Car
    Car car = new Car(engine);  // Pass the Engine instance to the constructor
    car.drive();  // Call the drive method to use the Engine
}

}

Python

class Engine: def start(self): print("Engine started")

class Car: def init(self, engine): self.engine = engine # Declaring a dependency on Engine

def drive(self):
    self.engine.start()  # Using the injected Engine dependency
    print("Car is driving")

Main execution

engine = Engine() # Create Engine object (dependency)

Injecting Engine dependency when creating Car

car = Car(engine) # Pass the Engine instance to the constructor car.drive() # Call the drive method to use the Engine

JavaScript

class Engine { start() { console.log('Engine started'); } }

class Car { constructor(engine) { this.engine = engine; // Declaring a dependency on Engine }

drive() {
    this.engine.start();  // Using the injected Engine dependency
    console.log('Car is driving');
}

}

// Main execution const engine = new Engine(); // Create Engine object (dependency)

// Injecting Engine dependency when creating Car const car = new Car(engine); // Pass the Engine instance to the constructor car.drive(); // Call the drive method to use the Engine

`

Output

Engine started Car is driving

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.

C++ `

#include

class Engine { public: void start() { std::cout << "Engine started" << std::endl; } };

class Car { private: Engine* engine; // Declaring a dependency on Engine

public: // No constructor injection here. Using setter to inject dependency void setEngine(Engine* engine) { this->engine = engine; // Injecting dependency via setter method }

void drive() {
    engine->start();  // Using the injected Engine dependency
    std::cout << "Car is driving" << std::endl;
}

};

int main() { Engine engine; // Create Engine object (dependency)

// Create a Car object without providing the Engine immediately
Car car;

// Inject the Engine dependency using the setter method
car.setEngine(&engine);  // Set the dependency via the setter method
car.drive();  // Call the drive method to use the Engine
return 0;

}

Java

class Engine { public void start() { System.out.println("Engine started"); } }

class Car { private Engine engine; // Declaring a dependency on Engine

// No constructor injection here. Using setter to inject dependency
public void setEngine(Engine engine) {
    this.engine = engine;  // Injecting dependency via setter method
}

public void drive() {
    engine.start();  // Using the injected Engine dependency
    System.out.println("Car is driving");
}

}

public class Main { public static void main(String[] args) { Engine engine = new Engine(); // Create Engine object (dependency)

    // Create a Car object without providing the Engine immediately
    Car car = new Car();

    // Inject the Engine dependency using the setter method
    car.setEngine(engine);  // Set the dependency via the setter method
    car.drive();  // Call the drive method to use the Engine
}

}

Python

class Engine: def start(self): print("Engine started")

class Car: def init(self): self.engine = None # Declaring a dependency on Engine

# No constructor injection here. Using setter to inject dependency
def set_engine(self, engine):
    self.engine = engine  # Injecting dependency via setter method

def drive(self):
    self.engine.start()  # Using the injected Engine dependency
    print("Car is driving")

Create Engine object (dependency)

engine = Engine()

Create a Car object without providing the Engine immediately

car = Car()

Inject the Engine dependency using the setter method

car.set_engine(engine) # Set the dependency via the setter method car.drive() # Call the drive method to use the Engine

JavaScript

class Engine { start() { console.log('Engine started'); } }

class Car { constructor() { this.engine = null; // Declaring a dependency on Engine }

// No constructor injection here. Using setter to inject dependency
setEngine(engine) {
    this.engine = engine;  // Injecting dependency via setter method
}

drive() {
    this.engine.start();  // Using the injected Engine dependency
    console.log('Car is driving');
}

}

// Create Engine object (dependency) const engine = new Engine();

// Create a Car object without providing the Engine immediately const car = new Car();

// Inject the Engine dependency using the setter method car.setEngine(engine); // Set the dependency via the setter method car.drive(); // Call the drive method to use the Engine

`

Output

Engine started Car is driving

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.

C++ `

#include

class Engine { public: void start() { std::cout << "Engine started" << std::endl; } };

// Define an interface for injecting dependencies class EngineInjector { public: virtual void injectEngine(Engine* engine) = 0; // Method to inject the Engine dependency };

class Car : public EngineInjector { private: Engine* engine; // Declaring a dependency on Engine

public: // Implement the injectEngine method to set the Engine dependency void injectEngine(Engine* engine) override { this->engine = engine; // Dependency injected through the interface method }

void drive() {
    engine->start();  // Using the injected Engine dependency
    std::cout << "Car is driving" << std::endl;
}

};

int main() { Engine engine; // Create Engine object (dependency)

Car car;  // Create Car object
car.injectEngine(&engine);  // Inject dependency via the injectEngine() method
car.drive();  // Call the drive method to use the Engine
return 0;

}

Java

class Engine { public void start() { System.out.println("Engine started"); } }

// Define an interface for injecting dependencies interface EngineInjector { void injectEngine(Engine engine); // Method to inject the Engine dependency }

class Car implements EngineInjector { private Engine engine; // Declaring a dependency on Engine

// Implement the injectEngine method to set the Engine dependency
@Override
public void injectEngine(Engine engine) {
    this.engine = engine;  // Dependency injected through the interface method
}

public void drive() {
    engine.start();  // Using the injected Engine dependency
    System.out.println("Car is driving");
}

}

public class Main { public static void main(String[] args) { Engine engine = new Engine(); // Create Engine object (dependency)

    Car car = new Car();  // Create Car object
    car.injectEngine(engine);  // Inject dependency via the injectEngine() method
    car.drive();  // Call the drive method to use the Engine
}

}

Python

class Engine: def start(self): print("Engine started")

Define an interface for injecting dependencies

class EngineInjector: def inject_engine(self, engine): pass # Method to inject the Engine dependency

class Car(EngineInjector): def init(self): self.engine = None # Declaring a dependency on Engine

# Implement the injectEngine method to set the Engine dependency
def inject_engine(self, engine):
    self.engine = engine  # Dependency injected through the interface method

def drive(self):
    self.engine.start()  # Using the injected Engine dependency
    print("Car is driving")

if name == "main": engine = Engine() # Create Engine object (dependency)

car = Car()  # Create Car object
car.inject_engine(engine)  # Inject dependency via the inject_engine() method
car.drive()  # Call the drive method to use the Engine

JavaScript

class Engine { start() { console.log('Engine started'); } }

// Define an interface for injecting dependencies class EngineInjector { injectEngine(engine) {} // Method to inject the Engine dependency }

class Car extends EngineInjector { constructor() { super(); this.engine = null; // Declaring a dependency on Engine }

// Implement the injectEngine method to set the Engine dependency
injectEngine(engine) {
    this.engine = engine;  // Dependency injected through the interface method
}

drive() {
    this.engine.start();  // Using the injected Engine dependency
    console.log('Car is driving');
}

}

const engine = new Engine(); // Create Engine object (dependency)

const car = new Car(); // Create Car object car.injectEngine(engine); // Inject dependency via the injectEngine() method car.drive(); // Call the drive method to use the Engine

`

Output

Engine started Car is driving

Advantages

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

Limitations

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