SOLID: Dependency Inversion
Pora na ostatni artykuł z naszej serii o SOLID. Tym razem opisujemy literę D, która rozwija się w Dependency Inversion Principle, czyli Zasada odwrócenia zależności. Mówi ona o tym, że moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Ponadto, abstrakcje nie powinny zależeć od szczegółów, ale szczegóły od abstrakcji. Co dokładnie kryje się za tym tajemniczym zdaniem? Sprawdź sam czytając ten wpis!
Dobrze zgrany zespół
Dla lepszego zrozumienia spróbujemy posłużyć się jakąś metaforą wziętą z życia. Wyobraźmy sobie, że mamy orkiestrę, gdzie każdy muzyk reprezentuje moduł niskopoziomowy, a dyrygent wysokopoziomowy.
W sytuacji, gdyby moduły wysokopoziomowe zależały bezpośrednio od modułów niskopoziomowych, to dyrygent orkiestry musiałby dyrygować bezpośrednio każdym instrumentalistą, kontrolując każdy, nawet niewielki detal.
A co gdybyśmy zastosowali Zasadę odwrócenia zależności?
Dyrygent mógłby skupić się tylko na dyrygowaniu, nie wdając się w szczegółowe kierowanie każdym muzykiem. Zarówno on jak i instrumentaliści posługują się zapisem nutowym, który jest odpowiednikiem abstrakcji w kodzie. Dzięki temu, na przykład trębacz i kontrabasista mogą posługiwać się tą samą notacją, nie zagłębiając się w szczegóły instrumentu towarzyszącego. Orkiestra staje się wtedy bardziej elastyczna i dostosowuje się do różnych utworów.
Sedno
Na czym zatem polega Dependency Inversion Principle?
Możemy wyróżnić dwa kluczowe fundamenty:
-
Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. ALE zarówno jedne, jak i drugie powinny zależeć od abstrakcji.
-
Abstrakcje nie powinny zależeć od szczegółów, to szczegóły powinny zależeć od abstrakcji.
Jak możesz osiągnąć coś takiego w swoim kodzie?
Staraj się postawić na interfejsy i klasy abstrakcyjne. Pomogą Ci zmniejszyć ilości zależności do konkretnych implementacji w Twoim kodzie. Najlepiej będzie posłużyć się przykładem:
Wyobraźmy sobie, że mamy dwie klasy, jedna reprezentuje auto spalinowe (moduł niskiego poziomu), a druga kierowcę (moduł wysokiego poziomu):
class GasolineCar { public void startEngine() { } } class Driver { private final GasolineCar car; public Driver(GasolineCar car) { this.car = car; } public void startCar() { car.startEngine(); } }
Klasa kierowcy została powiązana z konkretną implementacją jaką jest auto spalinowe. Moduł wysokopoziomowy, zależy od modułu niskopoziomowego. Jest to błąd, który łamie zasadę DIP!
Dlaczego ?
Wyobraźmy sobie teraz, że kierowca musi nagle przesiąść się na auto elektryczne:
class ElectricCar { public void startEngine() { } }
W tym momencie moglibyśmy dodać nowe pole w klasie Driver, ponieważ jej poprzednia implementacja nie daje możliwości kierowania autami elektrycznymi. Łamiemy wtedy zasadę Open-Closed, ponieważ byłaby to modyfikacja istniejącego już kodu. Jak w takim razie zrobić to dobrze ?
Wprowadzając interfejs Car:
interface Car { void startEngine(); }
który zostanie zaimplementowany przez klasy GasolineCar oraz ElectricCar:
class GasolineCar implements Car { @Override public void startEngine() { } } class ElectricCar implements Car { @Override public void startEngine() { } }
Klasa Driver będzie wtedy wyglądać w taki sposób:
class Driver { private final Car car; public Driver(Car car) { this.car = car; } public void startCar() { car.startEngine(); } }
Jak widać pozbyliśmy się zależności od konkretnej implementacji, teraz każda zmiana w klasach, które implementuja ten interfejs staje się przezroczysta dla klasy Driver.
Plusy i minusy
Podsumowanie
Zasada odwrócenia zależności stanowi istotny element zwinnej architektury oprogramowania, przyczyniając się do rozdzielenia odpowiedzialności między różnymi warstwami aplikacji.
W rzeczywistych systemach często moduły wysokopoziomowe definiują ogólną logikę biznesową, natomiast te niskopoziomowe są odpowiedzialne za konkretne operacje. Poprzez wprowadzanie abstrakcji i stosowanie wstrzykiwania zależności, tworzymy luźne powiązania między tymi warstwami, co ułatwia późniejsze modyfikacje i rozwijanie systemu.
Taka elastyczność jest bardzo pożądana w dzisiejszym dynamicznym środowisku. Dzięki niej możemy szybko dostosować się do nagłych zmian w wymaganiach biznesowych.