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();
}
}

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:
ProductionControllernie zależy od konkretnej klasy (ManualConcreteMachineczyAutomaticConcreteMachine).- 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();
}
}
Kiedy zasada jest ważna?
- Gdy system ma się rozwijać (dodawanie nowych urządzeń, etapów produkcji, funkcji).
- W projektach z wieloma warstwami (np. kontrolery, serwisy, dostęp do bazy danych).
Na co zwracać uwagę:
- Używaj interfejsów lub klas abstrakcyjnych jako punktów zależności.
- Stosuj wstrzykiwanie zależności (Dependency Injection) przez konstruktor lub setter.
Trudności:
- Początkowo kod może wydawać się bardziej złożony (więcej klas i interfejsów),
- Jednak w dłuższej perspektywie przynosi ogromne korzyści w utrzymaniu i testowaniu kodu.
