Zasada Odwrócenia Zależności



Definicja

Zasada odwrócenia zależności (Dependency Inversion Principle, DIP) mówi, że:

Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Obydwa powinny zależeć od abstrakcji.

W praktyce oznacza to, że zamiast bezpośrednio tworzyć i używać konkretnych klas, powinniśmy opierać się na interfejsach lub klasach abstrakcyjnych.
Dzięki temu kod staje się luźno powiązany, elastyczny i łatwy do modyfikacji.

Przykład z życia

W fabryce prefabrykatów żelbetowych mamy system produkcji, który koordynuje różne etapy wytwarzania elementów:
zbrojenie, betonowanie, transport i magazynowanie.

Jeżeli kontroler produkcji (moduł wysokiego poziomu) bezpośrednio tworzy obiekty konkretnych maszyn, to w momencie zmiany typu maszyny (np. wymiany na nowy model) – trzeba modyfikować kod kontrolera.

Zamiast tego – kontroler powinien komunikować się z maszynami przez interfejs (abstrakcję),
a konkretne implementacje maszyn można łatwo podmieniać bez ingerencji w logikę sterującą.

Przykłady przed i po zastosowaniu zasady

PRZED

Problem:

Kod jest mało elastyczny i trudny do testowania.

ProductionController jest ściśle zależny od klasy ConcreteMachine.

Jeśli chcemy dodać nowy typ maszyny (np. AutomaticConcreteMachine), musimy zmienić kod kontrolera.

class ConcreteMachine {
    public void pourConcrete() {
        System.out.println("Pouring concrete into the mold...");
    }
}



class ProductionController {
    private ConcreteMachine machine = new ConcreteMachine();

    public void startProduction() {
        machine.pourConcrete();
    }
}



public class Main {
    public static void main(String[] args) {
        ProductionController controller = new ProductionController();
        controller.startProduction();
    }
}
Designed by Freepik

PO

Zastosujmy zasadę Dependency Inversion Principle, wprowadzając interfejs opisujący ogólne zachowanie maszyny.
Kontroler będzie zależeć od tego interfejsu – a nie od konkretnej klasy.

Dlaczego teraz działa lepiej:

  • ProductionController nie zależy od konkretnej klasy (ManualConcreteMachine czy AutomaticConcreteMachine).
  • Zależność jest odwrócona – kontroler zna tylko interfejs ConcreteWorker.
  • Można łatwo wprowadzić nową maszynę, np. SmartConcreteMachine, bez modyfikowania kontrolera.
// Abstrakcja (interfejs)
interface ConcreteWorker {
    void pourConcrete();
}

// Konkretne implementacje
class ManualConcreteMachine implements ConcreteWorker {
    @Override
    public void pourConcrete() {
        System.out.println("Pouring concrete manually...");
    }
}

class AutomaticConcreteMachine implements ConcreteWorker {
    @Override
    public void pourConcrete() {
        System.out.println("Pouring concrete automatically with robotic arm...");
    }
}

// Moduł wysokiego poziomu (zależny od abstrakcji, a nie od implementacji)
class ProductionController {
    private ConcreteWorker worker;

    // Wstrzyknięcie zależności (Dependency Injection)
    public ProductionController(ConcreteWorker worker) {
        this.worker = worker;
    }

    public void startProduction() {
        System.out.println("Starting production process...");
        worker.pourConcrete();
    }
}

// Klasa główna – przykład użycia
public class Main {
    public static void main(String[] args) {
        // Możemy łatwo wymieniać implementacje bez zmiany kontrolera
        ConcreteWorker manual = new ManualConcreteMachine();
        ConcreteWorker automatic = new AutomaticConcreteMachine();

        ProductionController controller1 = new ProductionController(manual);
        ProductionController controller2 = new ProductionController(automatic);

        controller1.startProduction();
        controller2.startProduction();
    }
}