Odwracanie, wstrzykiwanie – pora rzucić okiem na nie. Część 1
W kwietniu na jednym z blogów poruszany był temat odwracania (inwersji) w kontekście tworzenia oprogramowania. Dotyczyło to takich zagadnień (pozwolę sobie na wstępie użyć ich angielskich nazw) jak Inversion of Control (w skrócie IoC) oraz Dependency Inversion Principle (DIP). Przy okazji tego drugiego odniesiono się też do Dependency Injection (DI), które jest do niego podobne brzmieniowo, ale to jedyne co je ze sobą łączy, prawdziwy bowiem związek Dependency Injection ma z pierwszym z zagadnień, czyli Inversion of Control. Jak widać powstaje tutaj swoisty trójkąt. I jak to w trójkątach, pojawiają się komplikacje ;).
Przy okazji lektury przywołanego wyżej wpisu pomyślałem sobie, że warto byłoby temat poruszyć bardziej szczegółowo, dotrzeć do korzeni, zobaczyć skąd się to wszystko wzięło. Postanowiłem zacząć od Inversion of Control, gdyż ma powiązania zarówno z Dependency Inversion Principle jak i Dependency Injection. O nim zatem z niniejszym wpisie, pozostałym dwóm poświęcę wpisy oddzielne.
Ciekawa sprawa z tym Inversion of Control. Tłumacząc to pojęcie na polski wyjdzie nam albo Odwracanie Kontroli, albo Odwracanie Sterowania, jak się jednak za chwilę okaże, oba te tłumaczenia całkiem udatnie wyrażają to, w czym rzeczone odwracanie się przejawia. Równie ciekawa w IoC jest też trudność z jego kategoryzacją. Wikipedia klasyfikuje je zarówno jako paradygmat, jak i wzorzec (projektowy lub architektury). Na swój użytek klasyfikuje IoC jako podejście do programowania. Najłatwiej jednak będzie wyrobić sobie opinię, co to właściwie jest, po prostu się z nim zapoznając.
Rozpatrzmy następujący program:
string firstName, lastName; Console.Write("Wprowadź imię: "); firstName = Console.ReadLine(); Console.Write("Wprowadź nazwisko: "); lastName = Console.ReadLine(); if (Exists(firstName, lastName)) Console.WriteLine("Znam Cię!"); else Console.WriteLine("Nie znam Cię!");
Cały jego przebieg jest pod ścisłą (tego programu) kontrolą lub mówiąc inaczej sterowanie jego przebiegiem zależy wyłącznie od struktury programu, kolejności, w jakiej ułożono instrukcje.
Sytuacja zmieni się jednak diametralnie, kiedy zamiast pobierać dane z konsoli, skorzystamy z dowolnego systemu przetwarzania formularzy. W jego przypadku dowolna kolejność wprowadzenia danych jest oczywistością. Tak samo jak dowolny również będzie moment, w którym wprowadzone dane zostaną użyte do sprawdzenia czy dana osoba istnieje. Tym samym oprogramowanie, które tworzymy, przestaje być przez nas kontrolowane w pełni. Musimy dostosować się do schematu polegającego na tym, że w odpowiednim momencie (np. po naciśnięciu przycisku [Gotowe]) zostanie wywołany nasz kod, w którym sprawdzimy wprowadzone dane.
public void OnDataCompleted(string firstName, string lastName, INotifier notifier) { if (Exists(firstName, lastName)) notifier.notify("Znam Cię!"); else notifier.notify("Nie znam Cię!"); }
Oczywiście możemy zadecydować, w jaki sposób chcemy doprowadzić do wywołania kodu sprawdzającego (przycisk, a może zakończenie edycji dowolnego z pól formularza) konstruując odpowiednio ten formularz. Niemniej program nie będzie już przebiegał zgodnie z góry ustalonym scenariuszem, takim jak w pierwszym przykładzie kodu. Tym samym – ni mniej ni więcej – tracimy nad nim kontrolę. Coś innego zaczyna sterować jego przebiegiem. Nasz kod jest wywoływany dopiero wówczas, kiedy jest to konieczne, kiedy jakiś nadrzędny, nieznany nam kod tak zadecyduje.
Warto zwrócić tutaj jeszcze uwagę na istotną zmianę zachowania oprogramowania. W pierwszym przykładzie, w momencie wywołania Console.ReadLine(), program wstrzymywał swoje działanie i do znudzenia oczekiwał na to, aż użytkownik łaskawie wprowadzi porcję danych. I mógł tak czekać w nieskończoność. Działanie to przypominało trochę takie kompulsywne sprawdzanie czy czasami już czegoś nie wpisano (oczywiście dzieje się to wewnątrz metody ReadLine, niemniej – dzieje się). W drugim przypadku nasz kod nie uczestniczy już w nieustannym zerkaniu czy czasami nie skompletowano wszystkich danych – został z tego natręctwa „wyleczony” ;). Takie zachowanie przypomina, znane skądinąd, powiedzenie używane nagminnie przez agentów z Hollywood: „Nie dzwoń do nas, my zadzwonimy do ciebie„. W informatyce nazwano to regułą Hollywood. Można więc powiedzieć, że odwrócenie kontroli (sterowania) to zastosowanie reguły Hollywood.
Być może niektórzy z Was właśnie skonstatowali, że owo IoC to coś z czym stykają się od dawna, nawet o tym nie wiedząc. To prawda. Odwrócenie sterowania (kontroli) to w tej chwili – rzecz by można – standardowe podejście do tworzenia oprogramowania. W ten sposób działają przeróżne frameworki (większość robią one, wy tylko dodajecie coś od siebie), jego przejawem jest obsługa zdarzeń, a takim bardzo wyrazistym przykładem IoC jest metoda szablonowa – jeden z podstawowych wzorców projektowych (której być może też kiedyś poświecę wpis).
Oczywiście to nie wszystko, co można napisać o IoC jako takim, ale wystarczająco jak na zainicjowanie tematu. Sam temat będę oczywiście – jak napisałem na wstępie – rozwijał (w miarę możliwości czasowych – ci, którzy śledzą bloga zapewne zauważyli, że przycichł on ostatnio – cóż taki mamy klimat ;)).