Dependency Injection – profesjonalne kontenery

Jestem po dłuższej przerwie 🙂 przgotowuje się na prezentację na temat CQRS, która będzie w gdańsku wszystko będzie nagrane, wrzucę to na bloga 🙂 Oraz cały czas praca nad startupem wrze 🙂 Ale przejdźmy do rzeczy 🙂 tak jak obiecałem w poprzednim artykule, następny będzie już o tych profesjonalnch kontenerach, warto umieć ich używać bo możliwości są duże 🙂 Jedziemy 🙂


Wstęp

Na pytanie „Czemu by nie użyć własnego kontenera?” odpowiedziałem w poprzednim artykule o kontenerach we wstępie.

No nie do końca najlepszy … 🙂

W tym artykule opiszę pare kontenerów ich wady, zalety oraz podstawowe funkcjonalności, przy takim wyborze kontenerów IoC pisanie własnego kontenera jest po prostu bez sensu…

A co powinien umieć taki kontener? To już opiszę na konkretnych przykładach kontenerów 🙂


Zalety i wady kontenerów

Główną zaletą kontenerów jest na pewno to, że ułatwiają tworzenie instancji klas z wcześniejszym ich rejestrowaniem, żeby kontener wiedział o ich istnieniu.

Następną zaletą jest to, że ułatwiają pisanie testów(co dla mnie jest bardzo ważne) tak jak pokazałem w poprzednim artykule 🙂

A główną wadą jest to, że kontenery na początku są trudne do zrozumienia niestety … Niektórym się wydaje, że kontenery są zbędne bo tylko utrudniają zrozumienie aplikacji. Jednak z czasem gdy już się załapie o co chodzi, stanie się to łatwe 🙂 Postaram się, żeby po przeczytaniu tego artykułu było to łatwe 🙂


Ninject

Pierwszy w kolejce jest Ninject pisany przez Nate Kohari.

Więc co powinien mieć szanujący się kontener?

Na pewno mieć możliwość pobrania wszystkich klas danego interfejsu by móc wyświetlić to później za pomocą IEnumerable zrobimy to za pomocą Ninjecta 🙂

Pierwsze co powinniśmy zrobić to wykasować wszystko co było związane z naszym kontenerem z poprzedniego artykułu i w funkcji Main() w klasie WebServer zrobić coś takiego:

We wnętrzu Ninjecta, czyli w kernelu tworzymy instancję klasy Bindings w której są wszystkie zarejestrowane przez nas klasy.

Następnie do kolekcji IEnumerable pobieramy wszystkie instancje interfejsu ICharacter i wyświetlamy je w pętli foreach.

Wiadomo jeszcze, że w metodzie ResolveInterfaces(), która służy jak nasz taki konstruktor w klasie GameServer musimy pobrać te klasy, które wcześniej zarejestrowaliśmy.

A jak wyglądają zmiany w testach? Zmian jest bardzo niewiele. Niewielkiej zmianie ulega jedynie metoda TestSetup() w klasie GameServerClassTest.

Przechodzimy to meritum, czyli do tego jak są rejestrowane klasy. Zobaczmy klasę Bindings

Tutaj są rejestrowane wszystkie klasy.

Końcowy wynik wygląda tak:

W klasie Bindings jest jeden problem. Taki sposób rejestrowania nie jest najlepszy co jeśli mielibyśmy załóżmy… trzydzieści klas i trzydzieści róznych interfejsów? Trochę lipa… Trzeba by było wypisywać po kolei wszystkie klasy…

Ale zaraz… czy szanujący się kontener nie powinien mieć możliwości rejestrowania z automatu wszystkich klas w systemie? Na to pytanie zaraz odpowiem bo pokaże to na przykładzie kontenera StructureMap 🙂


StructureMap

Odpowiadając na powyższe pytanie – Tak, powinien mieć taką możliwość, ale pokaże jak to zrobić z wykorzystaniem kontenera jak w tytule tego punktu 🙂

Zmieniamy kod w tych samych miejsach jak w poprzednim przykładzie. Zacznijmy w funkcji Main().

Mamy taki kod:

I to wystarczy, żeby zarejestrować wszystkie klasy. A utworzenie instancji klasy GameServer to już inna bajka jest nam tylko potrzebna do wywołania metody ResolveInterfaces(), która też ulega zmianie na takie coś:

W kodzie produkcyjnym na szczęście można zrobić wszystko automatycznie. Tylko w testach musimy podać klasy jakie chcemy zarejestrować.

No i wynik oczywiście 🙂

Przejdźmy to kontenera Castle Windsor


Castle Windsor

Tutaj pokaże jedynie jak go użyć. Najpierw zobaczmy zmiany w funkcji Main()

Czyli najpierw rejestrujemy zależności, i tworzymy instancje klasy GameServer. Tutaj samą klasę GameServer też musimy zarejestować, żeby utworzyć jej instancję.

W metodzie ResolveInterfaces() są takie zmiany:

W testach wystarczy umieścić to samo co jest w funkcji Main() bez żadnych zmian 🙂

Cały czas tworzymy instancje klas, ale czy można zarządzać ich cyklem życia? Czyli np powiedzieć kontenerowi, żeby tworzył tylko jedną instację danego obiektu, albo instancję traktował jako singleton.

Przy okazji inną wielką zaletą kontenerów jest możliwość sprawnej pracy z nimi. Dla przykładu można zobaczyć jak wygląda debugowanie Castle Windsor. Niezły odlot 🙂 https://github.com/castleproject/Windsor/blob/master/docs/debugger-views.md

Nasz kontener z poprzedniego artykułu zawsze tworzył nową instancję obiektu co w większych systemach jest niedopuszczalne, pomijając już to, że zawsze na końcu działania aplikacji powinniśmy czyścić nasz kontener, ponieważ utworzone instancje zostają na zawsze w kontenerze, jeśli ich nie wyczyścimy. 🙂

Zarządzaniem cyklem życia naszych obiektów zrealizujemy za pomocą kontenera Unity.

Oczywiście wszystkie te kontenery są z platformy .Net w innych platformach są inne kontenery.


Unity

Po kolei, zanim będziemy zarządzać cyklem życia obiektów, najpierw zarejestrujemy obiekty w funkcji Main() w ten sposób:

Te obiekty, które daliśmy jako argumenty do metod rejestrujących określają czy obiekt będzie singletonem albo czy będzie singletonem zarejestrowanej klasy, który nie będzie się dzielił instancją z tzw parent container. Może to wydawać się pogmatwane, ale zaraz wszystko stanie się jasne najpierw zobaczmy jak będziemy sprawdzać czy obiekt jest singletonem czy nie.

W klasieLoginValidator utworzyłem zmienną publiczną i metodę, coś takiego:

To samo w klasach CharacterSkillPoints, PasswordValidator.

Utworzonym obiektem będziemy wywoływać metode CountNumberOfCalling(), jeśli obiekt bedzie singletonem powinno nam wyświetlać w konsoli liczbę większą od 1.

Zacznijmy od pierwszego obiektu TransientLifetimeManager, wpiszmy pod rejestracją klas coś takiego:

Oczywiście, żeby to zadziałało musimy jeszcze utworzyć obiekt klasy GameServer:

I zmienić metodę klasy ResolveInterfaces().

Wynik jest taki:

Czyli widzimy, że jeśli damy jako argument obiekt TransientLifetimeManager to będzie nam zawsze tworzył nową instancję klasy.

Przejdźmy do następnego ContainerControlledLifetimeManager ten obiekt będzie nam tworzył singletony. Czyli funkcjia Main():

Jak widzimy metoda CountNumberOfCalling() wywołała się dwa razy w tym samym obiekcie, czyli mamy singletona 🙂

Przejdźmy do ostaniego obiektu HierarchicalLifetimeManager działa tak samo jak poprzedni tylko jedna różnica jest taka, żeby można za pomocą tego obiektu stworzyć dziecko kontenera, które też stworzy singletona, ale nie będzie ono dzieliło się instancją ze swoim kontenerem. Na przykładzie powinno być bardziej zrozumiałe. 🙂

Tworzymy instancję singletona a następnie dziecko kontenera i za jego pomocą tworzymy kolejnego singletona.

Wynik jest taki:

I tak mniej więcej wygląda zarządzanie cyklami życia obiektów, przejdźmy to ostaniego kontenera a którego używam i używałem najczęściej 🙂


Autofac

Zobaczmy podstawowe użycia Autofaca, zacznijmy od funkcji Main():

I zmiany w metodzie ResolveInterfaces()

Zmiany w testach są takie same jak w funkcji Main().

Ale to nie koniec zalet gotowych kontenerów, kolejna zaleta jest np taka, że można zarejestrować fabryki podając jako zależność Func<Interface> lub delegatę. Tym sposobem możemy utworzyć zależność kiedy jest ona naprawdę potrzebna i również nie narażamy kontenera przez tworzące się komponenty. Zaraz podam przykład 🙂

Najpierw tworzymy klasę z delegatą:

Przez tą delegatę utworzymy naszą klasę DelegateClass w funkcji Main().

W Main tworzymy instancję delegaty i przekazujemy odpowiedni argument. Wynik jest taki:

Następny artykuł będzie w całości o Autofac, więc o tym punkcie będzie bardziej szczegółowo już z wykorzystaniem Func w następnym artykule nie tylko delegaty 🙂


Inne zalety kontenerów

No właśnie, czy to wszystkie zalety kontenerów? Oczywiście, że nie 🙂

Kontenery pozwalają nam jeszcze wykorzystać programowanie aspektowe. Wszedzie w jakich firmach byłem programowanie aspektowe było wykorzystywane w mniejszym lub większym stopniu. W skrócie programowanie aspektowe polega na oddzieleniu logiki np cachowania lub logowania od logiki biznesowej. To tak w wielkim skrócie. Osobiście uważam to za bardzo ciekawe rozwiązanie na pewno warto się tym zainteresować.

Tu jest trochę o programowaniu aspektowym: https://dzone.com/articles/aspect-oriented-programming-in-c-using-dispatchpro

Gotowe kontenery działają na pewno szybciej niż ten nasz. No i są pisane latami w zespołach naprawdę doświadczonych programistów.


Jaki kontener wybrać dla siebie?

No na pewno nie sugerować się szybkością, mi to obojętne czy będe miał utworząną instancję klasy w pare czy parenaście milisekund…

Raczej myśle, żeby najbardziej zwrócić uwagę na możliwości i na składnie kontenera. Na przykład metody Register i Resolve można zaimplementować na tyle różnych sposób ile jest programistów na ziemi a jednak w niektórych kontenerach składnia moim zdaniem jest zbyt skomplikowana…

I zawsze pamiętajcie, żeby w testach również zmieniać kod na ten kontener którego używacie 🙂


Podsumowanie

W następnym artykule opiszę dokładniej kontener Autofac, czyli ten którego używam najczęsciej 🙂

Link do githuba ze wszystkimi przykładami: https://github.com/Slaw145/DIContainers

Standardowo, przypominam o newsletterze, którym wysyłam powiadomienia o nowych wpisach oraz dodatkowe informacje na temat, ogółem mówiąc, świecie IT.

KONIECZNIE dołącz do społeczności DevmanCommunity na fb, części społeczności jest w jednym miejscu 🙂

-strona na fb: Devman.pl-Sławomir Kowalski

-grupa na fb: DevmanCommunity

Pytaj, komentuj pod spodem na końcu wpisu, podziel się nim, oceń go, rób co wolisz  

Do następnego! Do zobaczenia! 🙂

 
Jeśli ten wpis ci się przydał podziel się nim ze swoimi znajomymi :)

2
Post a comment

avatar
1 Comment threads
1 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
SlawekBartek Recent comment authors
  Subscribe  
newest oldest evaluated
Notify about
Bartek
Guest
Bartek

Bardzo fajny post, generalnie większość na plus, dwie rzeczy rzucają się w oczy:
– zdania nie są często poprawnie skonstruowane, zwracaj na to większą uwagę, bo czasami trzeba coś przeczytać 2 – 3 razy, żeby zrozumieć
– moim zdaniem niepotrzebne spore zaciemnienie kodu poprzez użycie „@string”