To już ostatni artykuł z serii o zasadach SOLID. Litera „D” w tym skrócie oznacza Dependency Inversion Principle (DIP), czyli Zasadę odwracania zależności.

O co chodzi z tym odwracaniem?

Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Oba powinny zależeć od abstrakcji (np. interfejsów).

Robert C. 'Uncle Bob’ Martin

To pierwsza część tej reguły. Druga natomiast mówi nam, że:

Abstrakcje nie powinny zależeć od szczegółów. To szczegóły (konkretne implementacje) powinny zależeć od abstrakcji.

Robert C. 'Uncle Bob’ Martin

Mówiąc inaczej: tworzone przez nas systemy powinniśmy odwoływać do abstrakcji (przy pomocy interfejsów bądź klas abstrakcyjnych) zamiast elementów konkretnych (implementacji).

DIP – po co i dlaczego?

Wyobraź sobie, że komputery 🖥️ 💻 są tak zaprojektowane, iż myszki 🖱️nie podłącza się do portu USB, tylko jest przylutowana do płyty głównej. Wówczas, jak łatwo się domyślić, ewentualna wymiana „gryzonia” byłaby bardzo problematyczna dla przeciętnego użytkownika. Płyta główna – moduł wysokopoziomowy – wymagałaby wprowadzenia bezpośrednich zmian ze względu na zależność od modułu niskopoziomowego, jakim w tym przypadku jest mysz.

Klasy/moduły wysokiego poziomu zazwyczaj odpowiadają za ważne decyzje strategiczne, jak również modele danej aplikacji. Stąd są one w dużym stopniu odpowiedzialne za działanie całego programu. Jeśli elementy wysokopoziomowe są zależne od niskopoziomowych, to zmiany w modułach niskiego poziomu powodowałyby konieczność zmian elementów na wyższych poziomach. Ponadto w takich sytuacjach wielokrotne wykorzystanie modułu wysokopoziomowego byłoby zwykle utrudnione. Gdy ta zależność zostanie odwrócona, to zdecydowanie łatwiej będzie można taki moduł wiele razy wykorzystać.

Dzięki DIP kod staje się bardziej elastyczny, co może zaowocować mniejszą trudnością we wprowadzaniu zmian.

DIP na przykładzie

Przykład zastosowania opisywanej w tym artykule zasady przedstawię w PHP .

Mamy tutaj stację pogodową i chcielibyśmy sprawdzić pogodę np. aktualną. Oto jak wygląda program przed zastosowaniem DIP.

<?php

class CurrentWeather
{
    public function getWeatherData()
    {
        return "Aktualna pogoda: słonecznie";
    }
}

class WeatherStation
{
    private CurrentWeather $currentWeather;

    public function __construct(CurrentWeather $currentWeather)
    {
        $this->currentWeather = $currentWeather;
    }

    public function showWeatherData()
    {
        echo $this->currentWeather->getWeatherData();
    }
}
 

Klasa wysokopoziomowa, WeatherStation jest zależna od klasy CurrentWeather, niskopoziomowej, konkretnej. Załóżmy, że np. chcielibyśmy sprawdzić pogodę, dajmy na to, z dnia jutrzejszego. Wówczas należałoby utworzyć kolejną klasę, dla przykładu TomorrowWeather. Problem w tym, iż bylibyśmy wtedy zmuszeni do modyfikacji klasy wysokopoziomowej, a tego właśnie chcemy uniknąć wdrażając DIP. Odwróćmy zatem zależność.

<?php

interface Weather
{
    public function getWeatherData();
}

class CurrentWeather implements Weather
{
    public function getWeatherData()
    {
        return "Aktualna pogoda: słonecznie";
    }
}

class TomorrowWeather implements Weather
{

    public function getWeatherData()
    {
        return "Pogoda w dniu jutrzejszym: lekkie zachmurzenie";
    }
}

class WeatherStation
{
    private Weather $weather;

    public function __construct(Weather $weather)
    {
        $this->weather = $weather;
    }

    public function showWeatherData()
    {
        echo $this->weather->getWeatherData();
    }
}
 

Teraz obiekt $weather odwołuje się już nie do konkretnej klasy, lecz abstrakcji, jaką w tym przypadku jest interfejs Weather. Dlatego też klasa wysokopoziomowa już nie jest zależna od klasy niskopoziomowej. Jest również spełniona druga część reguły: abstrakcja nie zależy od szczegółów, za to szczegóły zależą od abstrakcji.

Podsumowanie

Poleganie na abstrakcji (m.in. interfejsach), jak nakazuje DIP, rozdziela zależności modułów wysokopoziomowych od niskopoziomowych, co przyczynia się do redukcji zależności w aplikacji. Dzięki takiej praktyce, nasz kod staje się bardziej elastyczny, a zmiany łatwiejsze do wprowadzania. Nie możemy jednak ślepo ufać interfejsom i klasom abstrakcyjnym, ale powinniśmy polegać na nich z głową i stosować tam, gdzie jest to sensowne i konieczne.


0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *