Lecture 4 — Dependency Injection & IoC
Loose coupling with plain Java (before Spring annotations)
Video: Dependency Injection & IoC Explained | Loose Coupling | Full Course #4
Playlist index: 4
Goal
Implement the lecture’s Order / Notification example yourself in plain Java (no Spring yet).
By the end you will have felt:
- Tight coupling pain (
new EmailService()inside business code). - Programming to an interface.
- Dependency Injection via constructor and setter.
- Why this is called Inversion of Control — and what Spring will automate next lecture.
Do not only watch. Create a simple Maven (or plain Java) project named e.g. di-lab and type each stage. Keep stages in git commits or separate packages (stage1, stage2, …) so you can compare.
Concepts
Dependency
If OrderService needs EmailService to finish placeOrder(), then EmailService is a dependency of OrderService.
Tight coupling (the problem)
OrderService ──creates──► EmailService
Problems:
- Cannot switch to SMS/Popup without editing
OrderService - Hard to unit-test without sending real emails
OrderServicedoes two jobs: business logic and object creation (SRP smell)
Loose coupling via interface
OrderService ──depends on──► NotificationService (interface)
▲
EmailService / SmsService / …
Still incomplete if OrderService itself does new EmailService().
Dependency Injection (DI)
Someone outside creates the dependency and passes it in (constructor or setter).
OrderService only uses NotificationService; it does not create it.
Inversion of Control (IoC)
Control of creating and wiring objects moves from the class to an external assembler (main today, Spring container tomorrow).
Rough relationship used in the lecture:
| Term | Role |
|---|---|
| IoC | The broader idea — control of wiring is inverted |
| DI | A practical technique to achieve IoC |
| Spring IoC container | Automates creation, wiring, lifecycle of beans |
Injection styles (this lecture)
| Style | Idea |
|---|---|
| Constructor injection | Pass dependency in constructor (preferred for required deps) |
| Setter injection | setX(...) after construction |
| Field injection | Annotate fields — meaningful with Spring; deferred |
Practice lab — type this in your editor
Create packages under src/main/java, e.g. com.example.dilab.
Stage 1 — tight coupling (baseline)
EmailService.java
package com.example.dilab;
public class EmailService {
public void sendNotification() {
System.out.println("Email sent");
}
}OrderService.java (tightly coupled)
package com.example.dilab;
public class OrderService {
public void placeOrder() {
EmailService notification = new EmailService(); // problem
notification.sendNotification();
System.out.println("Order placed");
}
}Main.java
package com.example.dilab;
public class Main {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.placeOrder();
}
}Run it. Then ask: What must I edit to notify via SMS instead?
Stage 2 — introduce the interface (still creating inside)
NotificationService.java
package com.example.dilab;
public interface NotificationService {
void sendNotification();
}Implementations
package com.example.dilab;
public class EmailService implements NotificationService {
@Override
public void sendNotification() {
System.out.println("Email sent");
}
}package com.example.dilab;
public class SmsService implements NotificationService {
@Override
public void sendNotification() {
System.out.println("SMS sent");
}
}package com.example.dilab;
public class PopUpNotificationService implements NotificationService {
@Override
public void sendNotification() {
System.out.println("Popup shown");
}
}Update OrderService to use the interface type, but still new a concrete class inside — notice the design is only half-fixed.
public class OrderService {
private NotificationService notification = new EmailService(); // still creates
public void placeOrder() {
notification.sendNotification();
System.out.println("Order placed");
}
}Stage 3 — constructor injection (DI)
package com.example.dilab;
public class OrderService {
private final NotificationService notification;
public OrderService(NotificationService notification) {
this.notification = notification;
}
public void placeOrder() {
notification.sendNotification();
System.out.println("Order placed");
}
}Main becomes the assembler:
public class Main {
public static void main(String[] args) {
NotificationService notification = new EmailService();
// try: new SmsService(); new PopUpNotificationService();
OrderService orderService = new OrderService(notification);
orderService.placeOrder();
}
}Swap implementations in main only — do not change OrderService.
Stage 4 — setter injection
package com.example.dilab;
public class OrderService {
private NotificationService notification;
public OrderService() {}
public void setNotification(NotificationService notification) {
this.notification = notification;
}
public void placeOrder() {
if (notification == null) {
throw new IllegalStateException("NotificationService not injected");
}
notification.sendNotification();
System.out.println("Order placed");
}
}OrderService orderService = new OrderService();
orderService.setNotification(new SmsService());
orderService.placeOrder();Stage 5 — testability win (dummy / stub)
package com.example.dilab;
public class DummyNotificationService implements NotificationService {
@Override
public void sendNotification() {
System.out.println("Dummy notification (test double)");
}
}In tests (or a second main path), inject DummyNotificationService so order logic can be verified without real email/SMS.
What Spring will replace next
Today main is the IoC “container”:
- Create objects
- Wire dependencies
- Call business methods
Lecture 5: Spring’s IoC container does (1) and (2). Your code declares what depends on what; the container builds the graph.
Exercises
- Add
SlackNotificationService implements NotificationServiceand inject it via constructor — without editingOrderService’splaceOrderbody. - Write one paragraph: difference between depending on
EmailServicevsNotificationService. - Prefer constructor or setter for a required dependency? Why?
- Draw the control-flow arrow: who creates
EmailServicein Stage 1 vs Stage 3?
Self-check
- Is Dependency Injection a Spring-only feature?
- What is inverted in “Inversion of Control” in this lecture’s sense?
- Why does field injection not show up cleanly in plain Java the same way?
Answers: self-check-answers