Dependency inversion principle

Teoria

Odwracanie zależności to ostatnia z reguł SOLID, jest ona jedną z ważniejszych zasad programowania obiektowego i można pokusić się o stwierdzenie, że jest to fundament obecnego podejścia do pisania oprogramowania. Dwa jej główne założenia to:

  • moduły wyższego poziomu, nie powinny być zależne od tych z poziomu niższego, Jedne i drugie powinny zależeć od abstrakcji (np. interfejsów)
  • abstrakcje nie powinny zależeć od szczegółów, to szczegóły (konkretne implementacje) zależą od abstrakcji

Przykład

Rozważmy przykład z mojej aplikacji pogodowej (można podejrzeć w dziale portfolio). Najpierw złe podejście. Mamy klasę WeatherService, która jest odpowiedzialna za dostarczanie aktualnej pogody oraz prognozy na kilka kolejnych dni. Dane pobieramy z zewnętrznego serwisu pogodowego poprzez API. Weather to klasa zawierająca zestaw danych pogodowych.

public class Weather{

  //Weather data class
}


public class WeatherService{

    private WeatherClient weatherClient = new WeatherClient();


    public Weather getWeather(){
        return weatherClient.getWeather();
    }

    public List<Weather> getForecast(){
        return weatherClient.getForecast();
    }

}



public class WeatherClient{

    public Weather getWeather(){
    
              System.out.println("Returning today's weather data");
              return new Weather();
      
    }
    
    public List<Weather> getForecast(){
            System.out.println("Returning forecast data");
    
        return new List<Weather>;
    }
}

Jak widzimy na powyższym przykładzie, w klasie WeatherService mamy utworzoną instancje WeatherClient, która zawiera funkcjonalności z pobieraniem danych pogodowych z zewnętrznego API. Mamy tutaj bezpośrednią zależność modułu wyższego poziomu, od modułu poziomu niższego. Stwarza to też kolejny problem w momencie gdy chcielibyśmy rozbudować aplikację o pobieranie danych z innych serwisów pogodowych. W celu pozbycia się błędów, możemy wprowadzić pośredniczący interfejs, który będzie implementowany przez konkretne API. Natomiast w klasie WeatherService, przy pomocy techniki zwanej wstrzykiwaniem zależności, umieścimy wspomniany interfejs w konstruktorze.

public class Weather{

  //Weather data class
}

public interface IWeatherClient {

    Weather getWeather();
    List<Weather> getForecast();
}



public class WeatherService{

    private IWeatherClient iWeatherClient;

    
    public WeatherService(IWeatherClient iWeatherClient){
       this.iWeatherClient = iWeatherClient;
    }
    
    public Weather getWeather(){
        return weatherClient.getWeather();
    }

    public List<Weather> getForecast(){
        return weatherClient.getForecast();
    }

}



public class WeatherClient implements IWeatherClient{
    
    @Override
    public Weather getWeather(){
    
              System.out.println("Returning today's weather data");
              return new Weather();
      
    }
    
    @Override
    public List<Weather> getForecast(){
            System.out.println("Returning forecast data");
    
        return new List<Weather>;
    }
}

Dzięki takiemu zabiegowi, aplikacja stałą się dużo łatwiejsza w rozbudowie. Uzyskujemy rozdzielenie modułów przez zastosowanie dodatkowego poziomu abstrakcji, co wbrew pozorom tylko ułatwia zrozumienie programu i poprawia jego czytelność. Takie podejście zapewnia również łatwiejsze przenoszenie kodu do innych projektów.

Był to już ostatni z serii artykułów na temat zasad programowania obiektowego SOLID. Mam nadzieję, że w prosty sposób udało mi się przybliżyć Tobie zagadnienia związane z tą praktyką. Jeśli nie zapoznałeś się jeszcze z pozostałymi artykułami, to gorąco do tego zachęcam.

Dodaj komentarz