Kihagyás

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?

  1. Cserélhetőség: ugyanaz a NotificationService minden módosítás nélkül használ SmsSender-t, EmailSender-t, vagy egy teszt-mocknak való FakeSender-t. A választást a kliens (vagy a DI keretrendszer) hozza meg.
  2. 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.
  3. Egyszeri felelősség (SRP): a NotificationService csak az értesítés logikájával foglalkozik, nem azzal, hogy hogyan épül fel a SmsSender (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 new belü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");
    }
}