Zasada odwrócenia zależności – Dependency Inversion Principle (DIP)

Zasada ta polega na tym, że nie powinniśmy tworzyć ścisłych zależności między klasami na poziomie szczegółowym (np. implementacji), lecz korzystać z interfejsów lub abstrakcji, które definiują, jak te klasy współdziałają. Dzięki temu możemy łatwiej modyfikować poszczególne moduły bez konieczności zmieniania innych części systemu.

Kluczowe założenia:

  • Wysokopoziomowe moduły (czyli te, które realizują główną logikę aplikacji) nie powinny zależeć od niskopoziomowych modułów (czyli tych, które realizują konkretne szczegóły techniczne, np. odczytywanie danych z plików czy bazy danych). Obie warstwy powinny zależeć od abstrakcji (np. interfejsów lub klas abstrakcyjnych).
  • Abstrakcje nie powinny zależeć od szczegółów – to szczegóły powinny zależeć od abstrakcji.
class FileLogger {
    public void log(String message) {
        System.out.println("Logging to a file: " + message);
    }
}

class ReportGenerator {
    private FileLogger logger;

    public ReportGenerator() {
        this.logger = new FileLogger();
    }

    public void generate() {
        logger.log("Generating report...");
        // Logika generowania raportu
    }
}

Problem: Klasa ReportGenerator (moduł wysokopoziomowy) jest bezpośrednio zależna od klasy FileLogger (moduł niskopoziomowy). Jeśli zdecydujemy się zmienić sposób zapisywania logów (np. na bazę danych lub zewnętrzny system monitorowania), musimy zmodyfikować klasę ReportGenerator. To łamie zasadę Dependency Inversion.

Zastosowanie Zasady odwrócenia zależności

Aby zastosować zasadę Dependency Inversion, powinniśmy wprowadzić abstrakcję (np. interfejs), od której będą zależały zarówno moduły wysokopoziomowe, jak i niskopoziomowe. Dzięki temu możemy zmieniać szczegóły implementacji logowania bez modyfikowania kodu modułu wysokopoziomowego.

//1.Stworzenie interfejsu loggera

public interface Logger {
    void log(String message);
}

//2. Implementacje interfejsu loggera

public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Logging to a file: " + message);
    }
}

public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Logging to a database: " + message);
    }
}

//3. Modyfikacja klasy ReportGenerator, aby zależała od abstrakcji (interfejsu), a nie konkretnej implementacji

public class ReportGenerator {
    private Logger logger;

    // Wstrzykiwanie zależności przez konstruktor
    public ReportGenerator(Logger logger) {
        this.logger = logger;
    }

    public void generate() {
        logger.log("Generating report...");
        // Logika generowania raportu
    }
}

//4. Użycie ReportGenerator z różnymi loggerami

public class Main {
    public static void main(String[] args) {
        Logger fileLogger = new FileLogger();
        ReportGenerator reportGenerator = new ReportGenerator(fileLogger);
        reportGenerator.generate();

        Logger dbLogger = new DatabaseLogger();
        ReportGenerator reportGeneratorWithDbLogger = new ReportGenerator(dbLogger);
        reportGeneratorWithDbLogger.generate();
    }
}

Korzyści z zastosowania DIP:

  • Elastyczność: Możesz zmieniać szczegóły techniczne (np. sposób logowania) bez wpływu na logikę biznesową. Wstrzykiwanie zależności umożliwia łatwą zamianę modułów niskopoziomowych.
  • Łatwiejsze testowanie: Można łatwo podmienić implementację loggera na mock w testach jednostkowych, co ułatwia testowanie aplikacji.
  • Rozdzielność odpowiedzialności: Moduły wysokopoziomowe i niskopoziomowe są lepiej od siebie oddzielone, co prowadzi do bardziej modułowego i czytelnego kodu.

Podsumowanie

Zasada Dependency Inversion Principle (DIP) promuje niezależność wysokopoziomowych i niskopoziomowych modułów przez wprowadzenie abstrakcji, od których zależą oba poziomy. Pozwala to tworzyć bardziej elastyczne, testowalne i łatwe w utrzymaniu systemy, które mogą łatwo dostosowywać się do zmieniających się wymagań i technologii bez konieczności zmiany logiki biznesowej.