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