Przyszła pora na ostatnią literkę z mnemonika SOLID, ale to wcale nie oznacza, że jest ona najmniej ważna. D rozwijane jest jako Dependency Inversion

Przyszła pora na ostatnią literkę z mnemonika SOLID, ale to wcale nie oznacza, że jest ona najmniej ważna. D rozwijane jest jako Dependency Inversion Principle co tłumaczone jest jako Zasada Odwrócenia Zależności. Nazwa brzmi odrobinę tajemniczo, ale tak naprawdę opisuje ona dwie istotne reguły:

  1. Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu.
  2. Abstrakcje nie powinny zależeć od szczegółów (konkretnej implementacji). Z kolei szczegóły (implementacja) powinny zależeć od abstrakcji.

Obie reguły brzmią w chwili obecnej odrobinę tajemniczo, ale jak zobaczycie w dalszej części tekstu, niosą one ze sobą głębszy sens.   Dependency Inversion Principle Z pewnością niejednokrotnie programując, zdarzyło Wam się wygenerować kod, który posiadał sztywne powiązania. Np. utworzyliście klasę zależną do klasy głównej, ale jednocześnie nie wyprowadziliście interfejsu klasy podrzędnej. Typowy problem każdego programisty, który wraz ze wzrostem doświadczenia zdarza się coraz rzadziej. Okazuje się jednak, że samo stworzenie interfejsu czasem nie wystarczy by można było odtrąbić sukces. Niezwykle ważne bywa również miejsce, w którym się go tworzy oraz szersza perspektywa.   Tworząc interfejs powinniśmy myśleć przede wszystkim o klasie głównej, która będzie go konsumowała, a nie o klasie szczegółu, która będzie go implementowała. Trzymając się standardowej nomenklatury dla tego przypadku, wyjściowo mamy klasy: - Foo - Bar - Bar jest elementem podrzędnym dla Foo   Już na pierwszy rzut oka widoczne jest sztywne powiązanie pomiędzy klasami. Naturalnym krokiem będzie wydzielenie interfejsu IBar. W tym miejscu jednak powinniśmy pamiętać, by przede wszystkim zaspokajał on potrzeby klasy Foo, a dopiero później potrzeby klasy Bar, którą trzeba by ewentualnie dostosować do nowych realiów: - Foo - IBar - Bar - Foo wykorzystuje interfejs IBar, Bar implementuje interfejs IBar   Można to schematycznie przedstawić za pomocą diagramu, na którym widać wyraźny podział pomiędzy elementami, w którym to klasa Foo i interfejs IBar trzymają się razem, natomiast klasa szczegółów Bar, znajduje się gdzieś „na uboczu”. Przekładając to na biblioteki, bardzo często będzie tak, że klasa Foo oraz interfejs IBar znajdą się w jednej z bibliotek, natomiast konkretna implementacja pojawi się w innym miejscu. Za pomocą kontenera IoC dokonamy wiązania interfejsu oraz konkretnej implementacji klasy szczegółu w miejscu wdrożenia.   Przykład praktyczny W przykładzie praktycznym skorzystam raz jeszcze z kodu generatora raportów. Przypomnijmy sobie najpierw strukturę klasy ReportGenerator, którą możemy uznać za główną w naszym obecnym przypadku: [sourcecode language="csharp"] public class ReportGenerator { public void GenerateReport(IFileWriter fileWriter, DateTime date) { IDatabaseManager databaseManager = new MyDatabaseManager(); var reportData = databaseManager.GetReportData(date); fileWriter.Open(); fileWriter.WriteData(reportData); fileWriter.Close(); } } [/sourcecode] Wcześniej pomyślałem już o odpowiednich interfejsach, dlatego część problemu mamy z głowy. Raz jeszcze musimy pamiętać teraz, by odwrócić zależności tj. interfejsy powinny być przede wszystkim ważne z perspektywy potrzeb klasy ReportGenerator, a nie z perspektywy klas, które implementują interfejsy IDatabaseManager oraz IFileWriter. Bardzo dobrym posunięciem byłoby w tym przypadku nawet, przesunięcie klas implementacyjnych do osobnych bibliotek (tego oczywiście nie będzie widać na listingu, ale pozostawiam to Waszej wyobraźni:-). Taki krok pozwoliłby na lepsze pogłębienie zasady Dependency Inversion Principle i jednocześnie przyczyniłby się do zwiększenia użyteczności biblioteki bazowej. Jeśli kiedykolwiek wykorzystywaliście biblioteki portable (a teraz również .Net Core) to szybko przekonacie się, że im mniej kodu, w tak kluczowych miejscach tym lepiej. Trudniej w takiej sytuacji natknąć na kod specyficzny dla danej platformy.   Na koniec przypomnę również definicję wymienionych wyżej interfejsów: [sourcecode language="csharp"] public interface IFileWriter { void Open(); void WriteData(IEnumerable data); void Close(); } public interface IDatabaseManager { IEnumerable GetReportData(DateTime date); } [/sourcecode]

Jerzy Piechowiak

Altcontroldelete.pl

 

 Szukasz książki do C#? Kliknij TU lub zerknij poniżej: