Skip to content

Dependency injection

Dependency injection means a class does not create its dependencies itself. Instead, they are passed in from outside (e.g. via constructor or setter).

Builds on Interfaces

Stage 1core1 min read

What is dependency injection (DI)?

A class often needs other objects to do its job. These are its dependencies. With dependency injection (DI), the class does not create them with new. Someone else creates them and hands them in. The usual way is constructor injection: the dependencies arrive as constructor parameters. This shows clearly what the class needs, and the class keeps them for as long as it lives.

Think of it as “give me what I need” instead of “I’ll secretly create what I need.” DI works well with interfaces, but you can also inject a class that has no interface.

Why use it?

  • Testability: In a test, you can pass a fake (mock) object, so no real email gets sent.
  • Flexibility: You can switch implementations (e.g. Email vs SMS) without changing the class that uses them.
  • SOLID (DIP, the Dependency Inversion Principle): High-level code (the business logic) depends on abstractions (interfaces), not on low-level details such as a specific email sender. The caller decides which implementation to inject.

Example: constructor injection

// High-level service depends on an abstraction (interface)
public class NotificationService {
    private final NotificationChannel notificationChannel;

    // Dependency is INJECTED via constructor — we don't do 'new EmailService()' here
    public NotificationService(NotificationChannel channel) {
        this.notificationChannel = channel;
    }

    public void notify(String msg) {
        notificationChannel.send(msg);
    }
}
// Caller (e.g. Main) decides which implementation to use
NotificationChannel email = new EmailService();
NotificationService service = new NotificationService(email);
service.notify("Hello");

// Or swap to SMS without changing NotificationService
NotificationService smsService = new NotificationService(new SMSService());
smsService.notify("Hello");
  • NotificationService only knows the NotificationChannel interface. It has no idea whether EmailService or SMSService is behind it. This is the inversion: the service depends on the interface, and the caller injects the real channel.

Where you see it in this repo

  • DIP GoodCode: NotificationService(NotificationChannel channel) — constructor injection. Main creates EmailService or SMSService and passes it into NotificationService. This is the main example of DI in the SOLID section. examples/ConceptsDemo.java shows the same idea: AlertService receives a Notifier in its constructor.

In this topic

Done with this topic?

How well do you know it?

PreviousInner and static nested classes