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 nie powinny zależeć od szczegółów. To szczegóły powinny zależeć od abstrakcji.
Systemy, które piszemy, powinny odnosić się do abstrakcji (poprzez zastosowanie interfejsów lub klas abstrakcyjnych), a nie konkretnych elementów. Dzięki stosowaniu się do zasady odwrócenia zależności minimalizujemy zależności (loose coupling) w aplikacji. Dzięki temu nasze aplikacje będą bardziej elastyczne, a w przyszłości zmiany będą łatwiejsze do wprowadzania.
SOLID - Dependency Inversion Principle (DIP) - Wszystko Co Powinieneś Wiedzieć o Zasadzie Odwrócenia Zależności
Przede wszystkim zasada DIP, mówi o tym, że powinniśmy zmniejszać zależności do konkretnych implementacji. Najlepiej uzyskać to polegając na interfejsach, wtedy mamy w kodzie małe zależności. Interfejsy są stabilne. To znaczy, jeżeli zrobimy jakąś zmianę w interfejsie, to ta zmiana jest powiązana również, ze zmianą w implementacjach tego interfejsu. Jeżeli jednak, zrobimy zmianę w konkretnej implementacji, to zazwyczaj nie potrzebujemy zmieniać naszego interfejsu. Co za tym idzie, interfejsy są stabilniejsze od implementacji. Dodatkowo pisząc kod oparty na interfejsach, staje się on bardziej testowalny. Możesz z łatwością pisać do takiego kodu testy jednostkowe, ponieważ za interfejs możesz podstawić jakąś fake'ową implementację (mock'a).
Kod niestosujący się do zasady odwrócenia zależności:
public class Employee
{
public string Name { get; set; }
}
public class EmployeeRepository
{
public void Add(Employee employee)
{
//Add employee to database
}
}
public class EmployeeService
{
private EmployeeRepository _employeeRepository = new EmployeeRepository();
public void Add(Employee employee)
{
_employeeRepository.Add(employee);
}
}
Zdefiniujmy na początek podstawy, czyli czym są moduły wysokopoziomowe, a czym moduły niskopoziomowe, bez tego nie zrozumiesz zasady odwrócenia zależności. W powyższym kodzie modułem wysokopoziomowym jest klasa EmployeeService, a modułem niskopoziomowym klasa EmployeeRepository. Moduł wysokopoziomowy w tym przykładzie zależy od modułu niskopoziomowego, ponieważ używa konkretnej implementacji, czyli klasy EmployeeRepository, a to właśnie jest łamaniem zasady DIP. Aby ten kod był zgodny z DIP, musimy właśnie odwrócić te zależności (jak mówi sama nazwa). Jak to najlepiej zrobić? Implementacja gdzie zależności będą odwrócone może wyglądać na przykład tak jak na przykładzie poniżej.
Kod stosujący się do zasady odwrócenia zależności:
public class Employee
{
public string Name { get; set; }
}
public interface IEmployeeRepository
{
void Add(Employee employee);
}
public class EmployeeRepository : IEmployeeRepository
{
public void Add(Employee employee)
{
//Add employee to database
}
}
public class EmployeeService
{
private IEmployeeRepository _employeeRepository;
public EmployeeService(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
public void Add(Employee employee)
{
_employeeRepository.Add(employee);
}
}
W powyższym przykładzie nasz serwis EmployeeService nie zależy już od konkretnej implementacji EmployeeRepository, a jedynie zależy od abstrakcji. W tym przypadku od interfejsu IEmployeeRepository. Zmiany w module niskopoziomowym nie mają wpływu na moduł wysokopoziomowy. Udało się w ten sposób odwrócić zależności. Teraz zgodnie z zasadą Dependency Inversion Principle, moduł wysokopoziomowy nie zależy już od modułu niskopoziomowego, a zależy od abstrakcji. Wysokopoziomowy moduł nie interesują zmiany, które zostaną wprowadzone w module niskopoziomowym. Ważne jest tylko to, aby implementowała powyższy interfejs.
Wstęp do Dependency Injection (DI)
Klasa EmployeeService przyjmuje w konstruktorze interfejs IEmployeeRepository. Możesz stworzyć instancję klasy w taki sposób:
public class Program
{
static void Main()
{
var employeeService = new EmployeeService(new EmployeeRepository());
}
}
Jednak lepszym rozwiązaniem jest stworzenie również interfejsu dla IEmployeeService i tworzenie instancji nowych klas poprzez użycie kontenera Dependency Injection (wstrzykiwanie zależności). Dzięki użyciu kontenera Dependecy Injection, po odpowiednim skonfigurowaniu, możesz operować w swojej aplikacji na interfejsach i rzadziej będziesz tworzył obiekty poprzez "new". O tym, czym jest i jak zaimplementować Dependency Injection postaram się napisać w jednym z przyszłych artykułów na blogu.
PODSUMOWANIE
Głównym celem stosowania Dependency Inversion Principle jest rozdzielenie zależności modułów wysokopoziomowych od niskopoziomowych poprzez stosowanie abstrakcji. Zasada ta mówi, że powinniśmy polegać na interfejsach, dzięki którym nasze aplikacji mają mniejsze zależności. Oczywiście nie powinniśmy zawsze bezgranicznie polegać tylko i wyłącznie na interfejsach, ale powinniśmy to robić z głową i stosować w tych miejscach, gdzie ma to sens. Do zastosowania DIP najlepiej korzystać z kontenerów Dependency Injection (DI).
Poprzedni artykuł - SOLID - Interface Segregation Principle (ISP) - Wszystko Co Powinieneś Wiedzieć o Zasadzie Segregacji Interfejsów .
Następny artykuł - Programowanie Zgodne z Regułami SOLID - Poradnik Dla Początkujących Programistów.