Dependency Injection (Függőséginjektálás)
Kategória: Modern minta (Modern Pattern)
Lényege és célja
A Dependency Injection (DI) lényege, hogy egy osztály a saját függőségeit nem maga állítja elő (new ...), hanem kívülről kapja meg – jellemzően konstruktorban, esetleg setteren keresztül. Az osztály így csak használja a függőséget egy interfészen át, de nem felelős a létrehozásáért.
Ez a Dependency Inversion Principle (a SOLID "D" betűje) gyakorlati megvalósítása: a magas szintű modul (pl. NotificationService) nem egy konkrét osztálytól (pl. SmsSender) függ, hanem egy absztrakciótól (MessageSender interfész).
Mit nyerünk vele?
- Cserélhetőség: ugyanaz a
NotificationServiceminden módosítás nélkül használSmsSender-t,EmailSender-t, vagy egy teszt-mocknak valóFakeSender-t. A választást a kliens (vagy a DI keretrendszer) hozza meg. - Tesztelhetőség: unit tesztben a valódi függőség helyett egy mock vagy fake objektumot adunk be – nem kell tényleges SMS-t küldeni a teszthez.
- Egyszeri felelősség (SRP): a
NotificationServicecsak az értesítés logikájával foglalkozik, nem azzal, hogy hogyan épül fel aSmsSender(milyen API kulccsal, milyen konfigurációval).
DI vs. Service Locator vs. new
new SmsSender()az osztály belsejében: szoros csatolás, nem cserélhető, nem tesztelhető.- Service Locator (
Locator.get(SmsSender.class)): rejtett függőség, a kódból nem látszik, mire van szüksége az osztálynak. - DI: a függőség látható a konstruktor szignatúrájában, és a kliens dönti el, mit ad át. Ez a legtisztább.
Megjegyzés: A DI maga minta, nem keretrendszer. A Spring, a Guice, a CDI csak automatizálják az injektálást (pl. annotációkkal), de a minta működik ezek nélkül is – ahogy a példában is, kézzel példányosítva.
Mikor használjuk?
- Gyakorlatilag mindig, ahol egy osztálynak más osztályra van szüksége és az nem triviális érték-objektum. Ez a default; az
newbelül akkor indokolt, ha a függőség implementációs részlet és sosem kell cserélni. - Amikor a kódot egységtesztekkel akarjuk lefedni (és a teszteknél le akarjuk cserélni az IO-t, hálózatot, időt, véletlent stb.).
- Amikor egy keretrendszer (Spring, Quarkus, Micronaut) életciklusát szeretnénk kihasználni: a komponenseket a konténer példányosítja és köti össze.
Mermaid Diagram
A diagramon látható a Dependency Inversion: a NotificationService csak a MessageSender interfésztől függ, és a konkrét implementációt (SmsSender / EmailSender) a kliens (Main) adja át a konstruktornak.
classDiagram
class MessageSender {
<<interface>>
+sendMessage(message: String, receiver: String) void
}
class EmailSender {
+sendMessage(message, receiver) void
}
class SmsSender {
+sendMessage(message, receiver) void
}
class NotificationService {
-messageSender: MessageSender
+NotificationService(s: MessageSender)
+notifyUser(message: String, receiver: String) void
}
class Client
EmailSender ..|> MessageSender : "implementálja"
SmsSender ..|> MessageSender : "implementálja"
%% A magas szintű modul csak az interfésztől függ
NotificationService o--> MessageSender : "konstruktorban kapja"
Client --> NotificationService : "példányosít + injektál"
Client --> SmsSender : "kiválasztja"
Client --> EmailSender : "kiválasztja"
note for Client "new NotificationService(new SmsSender())" Forráskód
package modern.dependency_injection;
interface MessageSender {
void sendMessage(String message, String receiver);
}
class EmailSender implements MessageSender {
@Override
public void sendMessage(String message, String receiver) {
System.out.println("Email küldése a(z) [" + receiver + "] címre: [" + message + "]");
}
}
class SmsSender implements MessageSender {
@Override
public void sendMessage(String message, String receiver) {
System.out.println("SMS küldése a(z) [" + receiver + "] címre: [" + message + "]");
}
}
class NotificationService {
private final MessageSender messageSender;
NotificationService(MessageSender messageSender) {
this.messageSender = messageSender;
}
void notifyUser(String message, String receiver) {
messageSender.sendMessage(message, receiver);
}
}
class Main {
static void main() {
var notificationService = new NotificationService(new SmsSender());
notificationService.notifyUser("Hello World", "John Doe");
notificationService = new NotificationService(new EmailSender());
notificationService.notifyUser("Hello World", "John Doe");
}
}