Zasady SOLID: Odwracanie zależności

Wszystko powinno być tak proste, jak to tylko możliwe, ale nie prostsze.
Albert Einstein

Co ma wspólnego kopanie rowów z zasadami SOLID? A to już zależy od podejścia. Jako programista często przenoszę zagadnienia ze świata realnego do kodu. Tym razem będzie odwrotnie, wszystko w imię zrozumienia ostatnią z zasad SOLID – zasady odwracania zależności.

Poznanie wzorców projektowych wraz z całym zapleczem metod, jakie za nimi stoją było dla mnie dużym skokiem jakościowym w mojej karierze zawodowej. Miałem tą przyjemność, że mój pracodawca wysłał mnie, jako jeszcze bardzo nieopierzonego programistę na szkolenie, które prowadził Mac Michał, dzięki czemu w ciągu trzech dni mogłem szybko przyswoić dużą dawkę informacji. Oczywiście proces przyswajania, świeżo zdobytej wiedzy, trwał pewien czas, i trochę bolało ponowne przerobienie całego materiału w zaciszu własnego laptopa, ale z perspektywy czasu widzę, że się opłacało spędzić kilkanaście wieczorów nad klawiaturą. Zanim na łamach tego bloga zacznę pisać o wzorcach projektowych, chciałbym omówić zasady Solid. W tym wpisie chciałbym się pochylić nad ostatnią z zasad SOLID, Zasadzie Odwracania Zależności (ang. Dependency Inversion Principle).

Nazwa tej zasady może wydawać się dziwna, przez co, u mniej upierzonych programistów, może tworzyć mały mentlik myślowy. O co chodzi z odwracaniem zależności? W skoro te zależności mają być odwrócone to jak ma wyglądać przebieg nieodwrócony?

Podział na warstwy

Zasady Solid już trochę czasu mają, przez co warto wrócić do momentu, w którym powstawały. Były to czasy, gdzie paradygmat programowania obiektowego nie miał tak ugruntowanej pozycji jak obecnie, a prym wiodły strukturalne techniki analizy i tworzenia oprogramowania, a tam w dużym skrócie program wykonywał się linijka po linijce, aż napotkał na „punkt wyjścia”. Tworząc dowolny system, trzeba było napisać dużo linii kodu, dużo linii kodu koło siebie to mała czytelność, przez co powstała idea tworzenia podprogramów, które niejako ukrywały część funkcjonalności.

W ten sposób narodziła się idea tworzenia warstw, która w bardzo płynny sposób przeniknęła do świata programowania obiektowego, gdzie to poprzez kompozycję, możemy tworzyć warstwy separujące funkcjonalność na odpowiednim poziomie abstrakcji.

Wszystkie architektury obiektowe o przemyślanej strukturze składają się z precyzyjnie zdefiniowanych warstw, z których każda reprezentuje spójny zbiór usług udostępnianych za pośrednictwem dobrze zdefiniowanych i kontrolowanych interfejsów.
Grady Booch

I tu możemy zacząć mówić o zasadzie odwracania zależności. Jeżeli chcemy mieć oprogramowanie, które będzie czytelne, (będzie działać) i do tego będzie możliwość wprowadzania zmian, to idąc za słowami Booch’a, musimy mieć przemyślaną strukturę. Dobra idąc po linii najmniejszego oporu, jak potrzebujemy warstw to sobie je stwórzmy. Poniższy rysunek przedstawia najbardziej naiwną implementację „przemyślanej” struktury warstw. Jeżeli czytając to masz wrażenie, że już wcześniej to widziałeś, tylko nie za bardzo wiesz gdzie, to podpowiem Robert C. Martin – Agile Principles, Patterns and Practices.

model zależności modułów

Na chwilę pochylmy się nad tym rysunkiem i ustalmy wszystkie oczywiste fakty

  • Są trzy warstwy, warstwa strategii, mechanizmu i narzędzi,
  • Warstwa strategii wykorzystuje warstwę mechanizmu,
  • Warstwa mechanizmu wykorzystuje warstwę narzędzi.

I na upartego wrzucamy takie warstwy w kodzik, comit/push i w akompaniamencie zwycięskiego marszu idziemy do szefa po podwyżkę. A teraz ponownie wróćmy do słów Booch’a i ponownie przemyślmy naszą strukturę warstw. Mam trzy warstwy, które od siebie zależą i ładnie ze sobą współdziałają. Przynajmniej na razie.

Odwracanie zależności

I tu powstaje pytanie, co się stanie z warstwą strategii, która z natury rzeczy musi zawierać ważne strategicznie decyzje i modele biznesowe aplikacji, gdy będziemy musieli coś zmienić w warstwie narzędzi? Wszystko magicznie się posypie, i trzeba będzie trochę kodu pozmieniać, żeby to działało. W takim modelu warstwy wysokopoziomowe zależą od warstw niskopoziomowych, a w takim przypadku mówiąc o elastyczności oprogramowania mijamy się z prawdą.

I mały przykład z życia wzięty na bardzo niebezpieczny podział na warstwy

  1. Budowa ogrodzenia działki – warstwa strategii
  2. Kopanie wykopu pod fundamenty ogrodzenia – warstwa mechanizmu
  3. Kopanie łopatą wykopu pod fundamenty ogrodzenia – warstwa narzędzi

W takim wypadku przepływ ciężkiej fizycznej pracy będzie wyglądał następująco

  1. Weź łopatę
  2. Kop łopatą dół zgodnie z wyznaczoną trasą ogrodzenia
  3. Jeżeli wykopałeś zgodnie z planem zakończ pracę
  4. Nagrodź się piwem za dobrze wykonaną robotę

Jak widać strategicznie ważna rzecz, jaką jest budowa ogrodzenia jest uzależniona od wprawy w operowaniu łopatą. A co jak byśmy zamiast kopać łopatą chcieli wynająć firmę budowlaną, która wykopie nam to koparką? W naszym „przemyślanym” modelu warstw nie ma na to miejsca. Zatem jaki powinien być model warstw w naszym przypadku?

A) Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Obie grupy modułów powinny zależeć od abstrakcji.
B) Abstrakcja nie powinna zależeć od szczegółowych rozwiązań. To szczegółowe rozwiązania powinny zależeć od abstrakcji.

Tak to właśnie zdefiniował Wujek Bob i w tym właśnie leży sedno zasady odwracania zależności. Jak w takim razie będzie wyglądał nasz schemat warstw?

DIP-model-zaleznosc-od-abstrakcji

Mamy tu do czynienia z czymś, co Richard E. Sweet nazwał zasadą Hollywood, a mianowicie „Nie dzwoń do nas. To my do zadzwonimy do ciebie”. Moduły w niższych warstwach muszą zapewnić implementację dla interfejsów wyższego szczebla, więc możemy w jednym „pakiecie” zawrzeć zarówno implementację funkcjonalności danej warstwy jak i interfejs udostępniany niższym warstwom, przez co nasz moduł jest uniezależniony od zmian w warstwach niższych. Tak stworzona struktura warstw spełnia trzy podstawowe założenia dobrego kodu

  • Trwała
  • Elastyczna
  • Przenośna

Jak w takim razie powinna wyglądać implementacja zasady odwracania zależności w kodzie? O tym niebawem, na przykładzie wstrzykiwania zależności (ang. dependency injection). Jeśli chcesz zobaczyć jak to zrobić to zapraszam do subskrypcji mojego newsletteru.

Share on Facebook0Share on Google+0Tweet about this on Twitter0Share on LinkedIn0

Kamil Jóźwiak

Nazywam się Kamil Jóźwiak i jestem programistą w firmie Sii, specjalizuję się w technologi .Net. W codziennej pracy staram się łączyć wzorce, style architektoniczne, ale przedewszystkim zdrowy rozsądek. Miłośnik czystego kodu i automatyzacji wszystkiego co się da zaatomatyzować.

Zobacz też:
Zobacz też:
Jest wiele czynników, które mówią o jakości kodu, jednym z…