„High-level modules should not depend on low-level modules. Both should depend on abstractions.”
„Abstractions should not depend on details. Details should depend on abstractions.”

— Robert C. Martin

O co chodzi z DIP?

Zasada DIP mówi: moduły wysokiego poziomu (np. logika biznesowa) nie powinny być zależne od modułów niskiego poziomu (np. zapis do pliku, bazy danych, wysyłka maili). Oba powinny zależeć od abstrakcji.

Innymi słowy: jeśli kod „główny” zna konkretne klasy pomocnicze, to jest z nimi zbyt silnie związany. Gdy chcesz coś zmienić, np. inny system logowania – musisz zmieniać też logikę aplikacji. A to zły znak.

Przykład łamania DIP – konkretna zależność

Załóżmy, że mamy klasę Powiadamiacz, która wysyła maile:

public class Powiadamiacz {
    private EmailSerwis emailSerwis = new EmailSerwis();

    public void wyslijPowiadomienie(String tresc) {
        emailSerwis.wyslijEmail(tresc);
    }
}
public class EmailSerwis {
    public void wyslijEmail(String tresc) {
        System.out.println("Wysyłam email: " + tresc);
    }
}

Problem? Powiadamiacz zna konkretną klasę EmailSerwis. Jeśli chcesz wysyłać SMS-y, Slacka czy notyfikacje push – musisz przerabiać Powiadamiacz. Kod jest sztywny.

Przestrzeganie DIP – zależność od interfejsu

Najpierw tworzymy abstrakcję – interfejs:

public interface NadawcaPowiadomien {
    void wyslij(String tresc);
}

Implementacje:

public class EmailSerwis implements NadawcaPowiadomien {
    public void wyslij(String tresc) {
        System.out.println("Email: " + tresc);
    }
}

public class SmsSerwis implements NadawcaPowiadomien {
    public void wyslij(String tresc) {
        System.out.println("SMS: " + tresc);
    }
}

I teraz Powiadamiacz korzysta z interfejsu:

public class Powiadamiacz {
    private NadawcaPowiadomien nadawca;

    public Powiadamiacz(NadawcaPowiadomien nadawca) {
        this.nadawca = nadawca;
    }

    public void wyslijPowiadomienie(String tresc) {
        nadawca.wyslij(tresc);
    }
}

Użycie:

Powiadamiacz mailowy = new Powiadamiacz(new EmailSerwis());
mailowy.wyslijPowiadomienie("Zamówienie zostało wysłane");

Powiadamiacz smsowy = new Powiadamiacz(new SmsSerwis());
smsowy.wyslijPowiadomienie("Twoja paczka jest już w drodze");

Jak to się robi w praktyce?

W praktyce zasada Dependency Inversion jest realizowana poprzez przekazywanie zależności do klasy z zewnątrz, zazwyczaj przez konstruktor lub metodę ustawiającą (setter). Dzięki temu klasa nie tworzy obiektów pomocniczych samodzielnie, lecz otrzymuje je jako gotowe implementacje interfejsów. To pozwala łatwo podmieniać konkretne rozwiązania – np. w testach zamiast prawdziwego serwisu możesz przekazać atrapę (mock).

Korzyści z DIP

  • Większa elastyczność – zmieniasz implementacje bez grzebania w logice,
  • Łatwiejsze testowanie – możesz wstrzyknąć klasę testową (mock),
  • Niższe sprzężenie – klasy mniej od siebie zależą,
  • Większa rozszerzalność – dokładanie nowych form powiadomień nie wymaga zmian w istniejących klasach.

Podsumowanie

Dependency Inversion to zasada, która uwalnia Twój kod. Zamiast betonować zależności, tworzysz abstrakcyjne punkty styku. Klasa nie powinna wiedzieć kto coś robi – wystarczy, że wie co ma być zrobione.

Kod staje się jak gniazdko – możesz do niego podłączyć różne wtyczki. I o to chodzi w czystej architekturze.


Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *