SOLID – mnemonik, który mówi jak pisać poprawnie programy w językach obiektowych. Podążanie za tymi zasadami znacznie poprawia czytelność i możliwość utrzymywania kodu. Część piąta: Zasada odwrócenia zależności.
Dochodzimy do ostatniej zasady. Zasada odwrócenia zależności czyli Dependency inversion principle.
Spójrzmy co mówi DIP:
Wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych – zależności między nimi powinny wynikać z abstrakcji.
Aby lepiej to zrozumieć o co chodzi spójrzmy na nasz aktualny kod:
class Program { static void Main(string[] args) { var czolg = new Czolg() } } public class Czolg { ... public Czolg() { _pancerz = new Pancerz(); _karabin = new CelnyKarain(); _naped = new Kolka(); _lufa = DlugaLufa(); } ... }
Aktualnie tworząc klasę czołgu nie mamy wpływu no to, jaki ten czołg powstanie – z jakich elementów się będzie składać. To z jakich elementów ten czołg się będzie składać zapisane jest w konstruktorze czołgu. Czołg jest tutaj modułem wysokopoziomowym. Lufa, karabin, pancerz czy napęd są modułami niskopoziomowymi.
Wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych
W tym momencie nasz czołg zależy od modułów niskopoziomowych. Jak temu zaradzić?
zależności między nimi powinny wynikać z abstrakcji
Wpierw odpowiedzmy sobie na kilka pytań.
Czym jest abstrakcja w naszym programie? Abstrakcją są interfejsy lub klasy bazowe (np. lufa).
Jak wprowadzić zależności pomiędzy modułami wysokopoziomowymi a niskopoziomowymi? Używając odwróconego sterowania (Inversion of Control – IoC).
Czym jest IoC? Jest to wzorzec, który po zaimplementowaniu spowoduje, że nasz kod będzie działać zgodnie z zasadą odwróconego sterowania.
Jak działa IoC? W naszym przykładzie będzie używać typu IoC Creation inversion. Zasada działa jest prosta: moduły wysokopoziomowe „same sobie wybierają” jakich modułów niskopoziomowych chcą używać. Aktualnie czołg ma na stałe wpisane jaki ma mieć napęd czy lufę.
Wprowadźmy do naszego programu IoC. A dokładniej użyjemy najpopularniejszej implementacji wzorca IoC czyli wstrzykiwanie zależności (Dependency Injection).
class Program { static void Main(string[] args) { var pancerz = new Pancerz(); var karabin = new CelnyKarabin(); var naped = new Kolka(); var lufa = new DlugaLufa(); var czolg = new Czolg(pancerz, karabin, naped, lufa); } } public class Czolg { ... public Czolg(Pancerz pancerz, IStrzelajcy karabin, IJezdzacy naped, Lufa lufa) { _pancerz = pancerz; _karabin = karabin; _naped = naped; _lufa = lufa; } ... }
W tym momencie czołg sam sobie może wybrać jakich chce używać elementów. To my możemy kontrolować czołgiem z wysokiego poziomu, bez potrzeby wgłębiania się w implementację niskich poziomów.
W ten sposób nasz program spełnia teraz zasadę DIP. Używamy w tym celu DI, a dokładniej Constructor injection.
DIP a DI to nie to samo. DIP to Dependency inversion principle (zasada odwróconego sterowania), natomiast DI to Dependency Injection (wstrzykiwanie zależności). DI jest sposobem spełniania zasad odwróconego sterowania.
W naszym programie używamy jeszcze jednego wstrzykiwania zależności.
class Program { static void Main(string[] args) { ... var czolg = new Czolg(pancerz, karabin, naped, lufa); ISiejacyZniszczenie pocisk = new Pocisk(); czolg.LadujPociks(pocisk); } }
LadujPocisk jest przykładem DI Method injection. Z wysokiego poziomu mamy możliwość ustawić jaki pocisk chcemy załadować.
Jakie korzyści otrzymujemy dzięki używaniu DIP?
- System teraz jest bardziej elastyczny.
- Mamy większą pewność, że zmiana komponentu nie przyniesie niepożądanych skutków (wszystko działa na abstrakcjach).
- Kod jest bardziej przenaszalny na inne projekty.
SOLID – Zasada odwrócenia zależności. Linki.
Podstawy DIP
Wytłumaczenie IoC
Świetnie opisany DIP, IoC oraz DI
Seria artykułów o IoC oraz DI