Piąta, ostatnia z zasad SOLID to zasada odwrócenia zależności – Dependency Inversion Principle. Zasada ta mówi o tym, że moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych, i jedne i drugie powinny zależeć od abstrakcji. Abstrakcje natomiast nie powinny zależeć od szczegółów, to szczegóły powinny zależeć od abstrakcji. Odpowiedzmy na pytanie o to, czym są moduły wysokopoziomowe, a więc są to moduły (klasy), które wykorzystują inne moduły (klasy) do wykonania zadania. Z kolei moduły niskopoziomowe zawierają szczegółową implementację jakiegoś konkretnego zadania, które może być wykorzystane przez inne moduły. Modułami niskopoziomowymi są moduły wejścia/wyjścia, bazy danych, systemy plików, internetowe API lub inne moduły zewnętrzne, które komunikują się z użytkownikiem, sprzętem lub innymi systemami. Z kolei moduły wysokopoziomowe to zazwyczaj główna logika biznesowa aplikacji.
Dzięki stosowaniu zasady odwrócenia zależności minimalizujemy zależności (loose coupling) w aplikacji, dzięki czemu staje się ona bardziej elastyczna, a przyszłe zmiany łatwiejsze do wprowadzenia. Przejdźmy zatem do przykładu, który zilustruje złamanie zasady DIP.
Boss.java
package organizer.event;
public class Boss {
private TechnicalManager manager = new TechnicalManager();
public void hireManager() {
manager.doStuff();
}
}
Klasa Boss jest modułem wysokopoziomowym, który zależy od modułu niskopoziomowego jakim jest TechnicalManager. Istnieje więc bezpośrednia zależność między nimi. Wcielając się w rolę organizatora eventów możemy sobie wyobrazić, że będziemy także potrzebować innych osób odpowiedzialnych np. za zapewnienie kateringu, wystrój sali itp. Zatem zmiana managera będzie wiązać się ze zmianą kodu wewnątrz klasy Boss. W ten sposób naruszona zostanie zasada odwrócenia zależności -DIP oraz zasada otwarte/zamknięte – OCP. Rozwiązaniem tego jest wprowadzenie abstrakcji – w tym przypadku stworzymy interfejs Manager, który będzie posiadał metodę doStuff.
Manager.java
package organizer.event;
public interface Manager {
void doStuff();
}
TechnicalManager.java
package organizer.event;
public class TechnicalManager implements Manager{
public void doStuff() {
//provide technical equipment of the room e.g. computer with projector, sound system etc.
}
}
DecoratingManager.java
package organizer.event;
public class DecoratingManager implements Manager {
@Override
public void doStuff() {
//provide flowers,decorations etc.
}
}
Boss.java
package organizer.event;
public class Boss {
private Manager manager;
public Boss(Manager manager) {
this.manager = manager;
}
public void hireManager() {
manager.doStuff();
}
}
Main.java
package organizer;
import organizer.event.Boss;
import organizer.event.DecoratingManager;
import organizer.event.TechnicalManager;
public class Main {
public static void main(String[] args) {
Boss myBoss1 = new Boss(new TechnicalManager());
myBoss1.hireManager();
Boss myBoss2 = new Boss(new DecoratingManager());
myBoss2.hireManager();
}
}
PODSUMOWANIE
Dzięki zastosowaniu zasady odwrócenia zależności – DIP, nasz kod jest łatwiejszy do testowania, ponieważ aby zweryfikować działanie klasy Boss, wystarczy wprowadzić własną implementację interfejsu Manager. Głównym celem DIP jest rozdzielenie zależności modułów wysokopoziomowych od niskopoziomowych poprzez zastosowanie abstrakcji. Zasada ta mówi także, że powinniśmy wykorzystywać interfejsy, dzięki którym nasze klasy mają mniejsze zależności. Należy tutaj także wspomnieć o wstrzykiwaniu zależności – DI, które to działanie pomaga wprowadzić zasadę DIP do własnego kodu. W powyższym przykładzie zatrudnienie nowego managera nie oznacza, że musimy znać szczegóły implementacji jego metod, a tylko tworzymy nową instancję klasy Boss, przekazując jako argument odpowiedniego Managera. Dzięki temu kod nie musi być modyfikowany ale możemy go rozszerzać tworząc klasy managerskie, implementujące interfejs Manager i zajmujące się kolejnymi kwestiami organizacji eventów. Mamy więc tutaj także do czynienia z zasadą Otwarte/Zamknięte – OCP.