Posts tagged inversion of control

Wzorce odwracania zależności w aplikacji z modelem domeny

Chciałbym nawiązać tą notką do mojej pierwszej notki z tego bloga. Była to analiza dostępnych technik odwracania zależności w kontekście aplikacji o tzw. architekturze cebulowej. Od tego czasu moje poglądy na ten temat nieco się zmieniły, stąd nagląca potrzeba aktualizacji.

Dlaczego w ogóle zajmuję się tym tematem? Wujek Bob, w jednej ze swoich ostatnich notek, poruszył problem uzależnienia od technicznych aspektów związanych z odwróceniem zależności. Jako anty-przykład podał rozwiązanie, w którym kod aplikacji bezpośrednio odwołuje się do framework-u DI w celu pobrania zależnych obiektów. Jego post zwraca uwagę na problem: sukces wprowadzenia inversion of control do mainstream-u programowania sprawił, że wielu zaczęło tego wzorca nadużywać lub używać niezgodnie z intencjami autorów.

Tyle tytułem wstępu. Co dalej? Chciałbym przedstawić Wam swoje podejście do inversion of control w kontekście aplikacji zbudowanych w oparciu o model domeny. W takiej aplikacji możemy wyróżnić dwa główne powody, dla których chcielibyśmy odwrócić sterowanie:

  • Nie chcemy uzależniać się od konkretnej, mimo, iż i tak prawdopodobnie będzie tylko jedna implementacja, np. ponieważ chcemy zachować możliwość testowania jednostkowego z wykorzystaniem mock-ów.
  • Architektura rozwiązania zakłada w pewnym miejscu odwrócenie zależności, które zrealizowane jest poprzez wstrzyknięcie do zależnego komponentu interfejsu reprezentującego jego zależność

Pierwszy powód jest natury całkowicie technicznej. W mojej pracy najczęściej spotykam się z nim przy okazji tzw. repozytoriów, czyli obiektów, które stanowią abstrakcję magazynu przechowującego obiekty modelu domeny. Szansa, że w ciągu pierwszych kilku lat rozwoju produktu pojawi się potrzeba wymiany mechanizmu przechowywania tych danych jest, bądźmy realistami, naprawdę nikła. Niemniej jednak wielu (w tym ja) pracowicie definiuje interfejsy repozytoriów i umieszcza je w assembly modelu domeny. Dlaczego? Szczerze mówiąc nie znajduję dla siebie wytłumaczenia poza faktem, iż pozbycie się tych interfejsów dodałoby do moich klas komend (lub ogólniej, warstwy usługowej aplikacji) zależność od NHibernate. Pewnie jednak następnym razem spróbuje bez repozytoriów… A co z wspomnianym testowaniem jednostkowym? Moje testy i tak mają albo charakter jednostkowy i testują model w pamięci, ale integracyjny i wtedy wykorzystują bazę SQLite.

Drugi powód jest znacznie ciekawszy. Występuje zwykle na granicy modelu domeny. Różne obiekty modelu, w odpowiedzi na zlecone operacje, potrzebują przekazać pewne dane na zewnątrz. Ponieważ zależność od obiektów modelu do komponentów warstw wyższych byłaby niezgodna z architekturą, należy tę zależność odwrócić. Jak? Oto kilka technik:

Wstrzykiwanie zależności

Technika ta zakłada definiowanie interfejsów obiektów zależnych na poziomie modelu domeny. Poszczególne implementacje są wstrzykiwane do obiektów modelu za pomocą specjalnego frameworka. W tym momencie pojawiają się dwa problemy.

Pierwszy problem ma naturę techniczną. Wstrzykiwanie odbywa się zwykle na etapie tworzenia instancji. Jeśli więc wykorzystujemy gotowe narzędzie O/RM, takie jak NHibernate, musimy się “wpiąć” w jakiś punkt rozszerzenia (mając nadzieje, że takowy istnieje) i wykorzystać nasz ulubiony kontener DI do wstrzyknięcia zależności.

Drugi problem jest poważniejszy, ponieważ jego źródło ma naturę logiczną. Reprezentacja zależności w formie pola lub właściwości psuje czystość modelu. Przykład? Klasa Customer reprezentująca klienta ma metodę SendNotification wysyłającą do klienta e-mail z pogróżkami. W tym celu wykorzystuje komponent IEmailSender. Oto jak mógłby wyglądać ten scenariusz z wykorzystaniem wstrzykiwania przez właściwości:

public interface IEmailSender
{
   void SendEmail(string address);
}

public class Customer
{
   public string Name { get; set;}
   public string EmailAddress { get; set;}
   public IEmailSender EmailSender { get; set; }

   public void SendEmail()
   {
      EmailSender.SendEmail(EmailAddress);
   }
}

Problem polega na tym, że powyższy kod sugeruje, że częścią składową modelu klienta jest mechanizm wysyłania e-maili, co jest oczywiście nieprawdą. Czym powyższy kod różni się od przypadku, w którym komponent obsługi poczty elektronicznej byłby wykorzystany bezpośrednio (new EmailSender())? Z punktu widzenia modelowania, niestety niczym. Cechuje się od jedynie (albo aż) zwiększoną testowalnością.

Double Dispatch

Pominę w tym miejscu wstęp teoretycznym, dotyczący tego czym Double Dispatch jest, i jak się ma do wzorca Visitor. W kontekście DDD jego wykorzystanie polegałoby na tym, że do operacji obiektu modelu przekazujemy, jako argument, obiekt zależności. Tak to wygląda w kodzie:

public class Customer
{
   public string Name { get; set;}
   public string EmailAddress { get; set;}

   public void SendEmailThrough(IEmailSender sender)
   {
      sender.SendEmail(EmailAddress);
   }
}

Co zyskujemy? Wyraźny komunikat, że komponent wysyłający nie jest częścią modelu klienta, ale że ten model potrafi go wykorzystać w celu wysłania listu.

Service Locator

Bardzo podobny efekt można uzyskać stosując wzorzec lokalizatora usług:

public class Customer
{
   public string Name { get; set;}
   public string EmailAddress { get; set;}

   public void SendEmail()
   {
      ServiceLocator.Current.GetInstance<IEmailSender>()
         .SendEmail(EmailAddress);
   }
}

Różnica polega na tym, że to obiekt modelu domeny explicite inicjuje pobranie referencji do zależności. Dlaczego już nie uważam, że to jest dobry pomysł? Ponieważ Double Dispatch pozwala na pisanie bardziej wyrazistego kodu (kontrakt metody od razu specyfikuje jej zależności), a jednocześnie nie jest związany z problemami natury technicznej (jak wstrzykiwanie). Jest więc obiektywnie patrząc lepszy.

Domain Events

Po raz kolejny odwołam się tutaj do świetnej serii artykułów Udiego Dahana dotyczącej zdarzeń domenowych. Zdarzenia są bowiem kolejną techniką wypychania informacji z modelu pozwalającą na odwrócenie zależności. Dlaczego uważam, że zdarzenia są lepsze niż Double Dispatch? Ponieważ promują dobre praktyki związane z modelowaniem. Przyjrzyjmy się naszemu klientowi bardziej całościowo:

public class CheckUnpaidContractsCommand
{
   public IEmailSender EmailSender { get; set; }
   public void Invoke()
   {
      Customer c = //...
      if (c.CheckUnpaidContracts())
      {
         c.SendEmailThrough(EmailSender);
      }
   }
}

public class Customer
{
   public string Name { get; set;}
   public string EmailAddress { get; set;}
   public bool HasUnpaidContracts { get; set; }

   public void SendEmailThrough(IEmailSender sender)
   {
      sender.SendEmail(EmailAddress);
   }

   public bool CheckUnpaidContracts()
   {
      //...
      HasUnpaidContracts = true;
      return true;
   }
}

Komenda sprawdzenia zaległości orkiestruje wywołanie dwóch metod na obiekcie modelu, wyraźnie pogwałcając zasadę tell, don’t ask. Mimo, iż możemy tutaj zauważyć prawidłowe wykorzystanie wzorca Double Dispatch — zależność jest wstrzykiwana w obiekcie warstwy wyższej, a następnie przekazywana do wywołania metody warstwy niższej — kod nie jest koncepcyjnie optymalny. W jaki sposób pomogą nam zdarzenia? Otóż pozwalają one tworzyć modele o postaci:

sprawdź niezapłacone rachunki i, jeśli takowe są, opublikuj zdarzenie “klient ma zaległości”

Sprytne, prawda? I brzmi tak biznesowo, a nie technicznie. A w kodzie wygląda tak:

public class CheckUnpadiContractsCommand
{
   public void Invoke()
   {
      Customer c = //...
      c.CheckUnpaidContracts()
   }
}

public class SendEmailIfCustomerHasUnpaidContracts : IEventHandler<CustomerHasUnpaidContractsEvent>
{
   public IEmailSender EmailSender { get; set; }
   public void Handle(CustomerHasUnpaidContractsEvent @event)
   {
      EmailSender.SendEmail(@event.Subject.EmailAddress);
   }
}

public class Customer
{
   public string Name { get; set;}
   public string EmailAddress { get; set;}
   public bool HasUnpaidContracts { get; set; }

   public void CheckUnpaidContracts()
   {
      //...
      HasUnpaidContracts = true;
      DomainEvents.Publish(new CustomerHasUnpaidContractsEvent(this));
      return true;
   }
}

Infrastruktura zdarzeniowa, którą opublikował Udi (jedna prosta klasa) pozwala na zgrabną rejestrację event handler-ów. Model ma teraz jasno określoną odpowiedzialność — poinformować świat zewnętrzny, że klient ma zaległości. Kod komendy jest zaś prosty, taki jak powinien być od początku. Dodatkowym, niebagatelnym, zyskiem wynikającym z takiego podejścia jest wydzielenie kodu polityki powiadamiania dłużników e-mailem do osobnej klasy implementującej dobrze znany interfejs. Dzięki temu zmiana polityki (na np. powiadamianie SMSem) jest dziecinnie prosta i nie wiąże się z modyfikacją kod modelu domeny.

Istnieje tylko jeden problem związany z tym rozwiązaniem: zdarzenia domenowe nie pozwalają na pobieranie informacji zwrotnej — są jednokierunkowe.

Podsumowanie

Podsumowanie jest krótkie. Jeśli interakcja modelu z otoczeniem jest jednokierunkowa (lub da się sprowadzić do jednokierunkowej!) — zdarzenia. W przeciwnym wypadku — Double Dispatch. Oczywiście jest to ogromne uproszczenie i na pewno są przypadki, w których takie postępowanie nie jest optymalne. Jest ono jednak całkiem niezłą heurystyką, dlatego warto mieć je w pamięci.

VN:F [1.9.13_1145]
Rating: 4.8/5 (8 votes cast)

Inversion of Control w systemach zbudowanych w oparciu o obiektowy model domeny

Artykuł opisuje propozycję implementacji zasady Inversion of Control (IoC) w systemach OLTP zbudowanych w oparciu o obiektowy model domeny. Zakłada się, iż system ma być zbudowany zgodnie z ideą architektury cebulowej kładącej nacisk na enkapsulację i izolację zmienności.

W kolejnych notkach będę starał się przybliżyć aspekty implementacyjne zaproponowanego rozwiązania. Mówiąc po ludzku – będą żywe przykłady.

Wprowadzenie

Architektura cebulowa została zdefiniowana przez Jeffrey-a Palermo w połowie 2008 roku. Idea jest więc całkiem nowa. Palermo w serii artykułów (1), opublikowanych na swoim blogu, zaproponował nieco inne, od klasycznego, podejście do warstw w aplikacji. Główną różnicą pomiędzy starym, a nowym, podejściem jest położenie nacisku na enkapsulację i izolację zmienności. Palermo zauważył, że klasyczne uzależnienie warstwy biznesowej od warstwy danych stoi w sprzeczności z faktem, iż część biznesowa aplikacji jest zwykle dużo stabilniejsza, jeśli chodzi o zmienność, niż część bazodanowa (która zmienia się wraz z nowymi technologiami). Efektem tej sprzeczności jest konieczność modyfikacji (a przynajmniej rekompilacji) warstwy biznesowej (oraz wszystkich warstw wyższych) w momencie zmiany warstwy dostępu do danych. Jest to coś, czego powinno się próbować unikać.

W zaproponowanym modelu cebuli warstwy położone w głębi charakteryzują się mniejszą podatnością na zmiany, niż warstwy płytsze. Zależności mogą być jedynie zwrócone do wnętrza cebuli. W ten sposób warstwa bazodanowa znajduje się na samej powierzchni, a model domeny – w centrum. Jak nietrudno zauważyć, jest to forma Odwrócenia Zależności (2), wymaga więc odpowiednich technik projektowych, takich jak:

aby móc w ogóle funkcjonować.

Artykuł ten jest konkretną propozycją zastosowania wspomnianych mechanizmów w architekturze cebulowej. Jej celem jest maksymalne uniezależnienie komponentów wszystkich warstw od wszelkich elementów infrastrukturalnych, w szczególności od infrastruktury realizującej zasadę odwrócenia sterowania.

Architektura systemu

Przykład omówiony w artykule jest zaczerpniętym z literatury (5)  systemem wykonanym w oparciu o architekturę cebulową z obiektowym modelem domeny w centrum.

Model domeny ma być wykonany według najlepszych praktyk związanych z tym wzorcem (6). W szczególności powinien on wykazywać wysoki poziom Persistence Ignorance (PI). Aby zrealizować to wymaganie architektoniczne, podjęta została technologiczna decyzja o wykorzystaniu biblioteki NHibernate, jak najpopularniejszego rozwiązania gwarantującego bardzo wysoki poziom PI.

W momencie wyboru tej technologii pojawia się pierwsze ograniczenie, które należy wziąć pod uwagę projektując architekturę Dependency Inversion. Jest to wymóg posiadania przez klasy modelu domeny bezparametrowego konstruktora. Ograniczenie to jest narzucone bezpośrednio przez konkretną technologię infrastruktury persystencji, jednak jeśli przyjrzeć mu się dokładniej, można zauważyć, że istnieje spora szansa, że inne technologie przedstawią dokładnie takie samo ograniczenie. W szczególności Entity Data Model, który w wersji 2.0 powinien gwarantować zbliżony do NHibernate poziom PI (7), prawdopodobnie wprowadzi podobne wymaganie.

Wracając na poziom abstrakcyjnej architektury, kolejną licząc od centrum cebuli, warstwą jest warstwa logiki aplikacji. Jest to miejsce, w którym żyją obiekty realizujące logikę, która nie jest cechą domeny problemu, ale cechą rozwiązania pewnego problemu istniejącego w tej domenie. Z bardziej technicznego punktu widzenia możemy powiedzieć, że w warstwie tej znajdują się obiekty enkapsulujące atomowe operacje realizowane na obiektach modelu domeny.

Kolejną warstwą w drodze ku powierzchni jest warstwa usługowa. Obiekty tej warstwy grupują i orkiestrują wywołania poszczególnych obiektów logiki aplikacji w celu realizacji pewnego określonego przypadku użycia (lub jego fragmentu).

Na samej powierzchni cebuli znajdują się najczęściej zmieniające się elementy systemu: warstwa prezentacji oraz warstwa dostępu do danych. Nie są one dla nas interesujące z punktu widzenia tego artykułu, dlatego zostaną pominięte w dalszej części rozważań.

Warstwa modelu domeny

Jak zaznaczono na wstępie, w tej warstwie należy liczyć się z narzuconymi przez technologię ograniczeniami. Oprócz wspomnianego wymagania tworzenia instancji za pomocą bezparametrowego konstruktora, istnieje jeszcze jeden problem. Jest nim brak jednego wspólnego API do wstrzykiwania zależności. Każdy dostępny na platformie .NET kontener Inversion of Control definiuje swoje własne API. W skład tego API mogą (tak, jak w przypadku Unity) wchodzić atrybuty, które są wymagane do wstrzykiwania zależności przez własności. Ten szczególny przypadek powoduje, że wykorzystanie podejścia Property Injection uzależniłoby model domeny od framework-u IoC.

Jako, że Constructor Injection jest także wykluczone, jedynym rozsądnym rozwiązaniem dla modelu domeny jest rezygnacja z wygodnego wstrzykiwania zależności na korzyść wzorca Service Locator. W literaturze teoretycznej (3) można znaleźć stwierdzenie, że wzorzec ten wymaga, aby komponenty były zależne od konkretnej implementacji wzorca. Praktyka, na szczęście, okazuje się jednak zupełnie inna. Na platformie .NET powstała inicjatywa Common Service Locator (CSL) (8), której efektem było stworzenie wspólnego dla wszystkich ważnych kontenerów IoC interfejsu lokalizatora usług (9). Dzięki temu zależność od CSL praktycznie nic nie kosztuje, w kontekście przywiązania do konkretnej technologii.

Jak jednak obiekty modelu domeny mogą dostać się do lokalizatora usług? Rozwiązaniem tego problemu wydaje się być statyczne udostępnienie referencji do obiektu lokalizatora (10). Dzięki temu może on być pobrany z dowolnego miejsca. Jeśli chodzi o konkretną implementację, to bardzo rozsądne wydaje się uczynienie tego kontekstowego lokalizatora specyficznym dla aktualnego wątku. W wypadku środowiska WWW właścicielem lokalizatora będzie obiekt obsługi żądania Web, natomiast w wypadku środowiska WCF – kontekst wywołania usługi.

Warstwa logiki aplikacji

Obiekty warstwy logiki aplikacji pozostają całkowicie pod kontrolą twórców systemu. Definicja architektury zakłada, ich instancjonowanie przez leżącą wyżej warstwę usługową. Nie ma więc żadnych przeciwwskazań dla wykorzystania metody Constructor Injection. Jest ona polecana (w przeciwieństwie do Property Injection) ze względu na fakt, że wstrzykiwanie zależności przez konstruktor nie wymaga – w większości framework-ów – żadnych specyficznych zabiegów (poza zadeklarowaniem samego konstruktora), które przywiązałyby nas do konkretnej infrastruktury.

Na osobne rozpatrzenie zasługuje specyficzny przypadek obiektu logiki aplikacji, jakim jest fabryka. Z doświadczenia autora wynika, że wzorzec fabryki (11) jest często spotykany w tej warstwie. Jest on niezbędny, kiedy produkcja obiektów realizujących pewien interfejs ma bazować na pewnym zestawie argumentów. Niestety kontenery IoC nie pozwalają zwykle na przekazywanie argumentów do metody rozwiązującej zależności., niezbędne jest więc zastosowanie jakiegoś dodatkowego mechanizmu.

[Edit(10.01.2009)]
Rejestrowanie klas fabrykowanych w kontenerze i wykorzystanie klasy-fabryki jedynie do mapowania argumentów metody fabrykującej na nazwy mapowań kontenera wydaje się dobrym pomysłem. Idealnym zaś byłoby rejestrowanie implementacji w kontenerze przez klasę fabryki, gdyż to w wyłącznie w jej zakresie obowiązków jest znajomość wszystkich konkretnych implementacji fabrykowanej abstrakcji.
[/Edit]

Niestety przeszkodą jest brak standardowego, dla wszystkich kontenerów, interfejsu rejestracji mapowań. Gdyby jednak taki standard istniał, optymalne rozwiązanie polegałoby na rejestracji mapowań w kontenerze podczas konstrukcji obiektu fabryki oraz późniejsze ich wykorzystanie w metodzie fabrykującej. Dodatkowym udoskonaleniem byłoby wykorzystanie nowej instancji kontenera, która byłaby „potomkiem” instancji głównej (kontenery potomne są wspierane przez większość framework-ów IoC). Konstruktor klasy fabryki tworzyłby kontener potomny, i to w nim rejestrował mapowania obiektów fabrykowanych. Dzięki temu inne obiekty korzystające z głównego kontenera nie miałyby dostępu do mapowań tej fabryki.

Ponieważ jednak w chwili obecnej nie dysponujemy standardem dla wspomnianej funkcjonalności, proponuje się zastąpienie jej prostszym rozwiązaniem: klasa fabryki pobiera z Service Locator-a zależności wymagane przez fabrykowane obiekty. Takie podejście sprawia, że klasy konstruowane za pomocą fabryki wyglądają zupełnie tak samo, jak te konstruowane bezpośrednio. Nie bierze jednak ono pod uwagę jednej ważnej kwestii: współczesne kontenery IoC, oprócz funkcjonalności wstrzykiwania zależności, oferują także możliwość wstrzykiwania aspektów. Ponieważ jest to bardzo istotna kwestia, niezbędne jest znalezienie sposobu na umożliwienie programowania aspektowego także w kontekście obiektów produkowanych przez fabryki.

Jako rozwiązanie proponuje się wykorzystanie czystej funkcjonalności wstrzykiwania, jaką oferuje każdy z kontenerów. Funkcjonalność ta może być udostępniona klasie fabryki jako interfejs IObjectBuilder z metodą

T BuildUp<T>(T plainInstance)

od którego klasa ta może być zależna. Fabryka po stworzeniu odpowiedniej instancji wykonuje metodę BuildUp, wstrzykując wymagane aspekty do instancjonowanego obiektu.

Niestety, nie istnieje jedna wspólna definicja funkcjonalności wstrzykiwania  do istniejących obiektów, jak to ma miejsce w wypadku Service Locator-a. Interfejs ObjectBuilder należy zdefiniować w systemie na własną rękę. Jego implementacja w kontekście dowolnego z głównych kontenerów nie powinna być problemem.

Warstwa usługowa

Obiekty warstwy usługowej także znajdują się pod pełną kontrolą twórcy systemu. Mogą więc być instancjonowane za pośrednictwem kontenera IoC dostępnego z poziomu warstwy interfejsu użytkownika. W tym wypadku, analogicznie jak w kontekście obiektów logiki aplikacyjnej, preferowanym sposobem wstrzykiwania zależności jest Constructor Injection. Argumentacja stojąca za wyborem tego podejścia jest podobna – celem jest jak największa niezależność od infrastruktury.
W warstwie usługowej nie występują typowo obiekty realizujące wzorzec fabryki, więc nie ma potrzeby zajmowania się tym specyficznym przypadkiem.

Podsumowanie

Powyższe sekcje dają pewien pogląd na optymalizację sposobu realizacji zasady Inversion of Control w różnych warstwach aplikacji. Wyszczególniliśmy trzy metody implementacji IoC:

  1. Obiekt wstrzykujący zależności i aspekty do istniejących obiektów – IObjectBuilder
  2. Wzorzec ServiceLocator
  3. Constructor Injection za pośrednictwem kontenera

Jeśli jednak przyjrzeć się liście operacji udostępnianych przez kontenery IoC, np. Unity, okaże się, że wszystkie trzy powyższe metody są wspierane przez odpowiednie funkcje kontenera. Problemem nie jest zatem określenie, jakiego charakteru mają być biblioteki IoC wykorzystywane w poszczególnych warstwach, ale to, jaki widok na ten sam kontener ma być udostępniony obiektom tych warstw.

Stwierdzenie tego faktu ma znaczenie o tyle ważne, że sprowadza problem do znanego problemu odpowiedniego umiejscowienia kontenera Investion of Control w warstwowej aplikacji. Istnieje wiele dobrych rozwiązań dla tego zagadnienia, a ich omówienie nie jest przedmiotem tego artykułu.
Podsumowując należy podkreślić, że nie istnieje jeden zawsze dobry i uniwersalny sposób realizacji zasady Inversion of Control. Z uwagi na ograniczenia o charakterze technicznym, najlepsze rezultaty można osiągnąć umiejętnie łącząc różne podejścia tak, aby wykorzystać ich najlepsze cechy. W szczególności należy zwrócić uwagę na fakt, że najbardziej widowiskowy sposób wstrzykiwania zależności, jakim niewątpliwie jest Property Injection, nie został w ogóle wymieniony jako zalecane rozwiązanie. Jest to efekt zarówno problemów natury technicznej (wprowadzanie zależności), jak i koncepcyjnej (możliwość utworzenia obiektu w niepoprawnym stanie).

Bibliografia

  1. Palermo, Jeffrey. The Onion Architecture : part 1. Jeffrey Palermo (.com). Lipiec 29, 2008. http://jeffreypalermo.com/blog/the-onion-architecture-part-1/.
  2. Martin, Robert C. The Dependency Inversion Principle. C++ Report. Maj 1996, 8.
  3. Fowler, Martin. Inversion of Control Containers and the Dependency Injection pattern. [Online] Styczeń 14, 2004. http://martinfowler.com/articles/injection.html.
  4. —. Patterns of Enterprise Application Architecture. s.l. : Addison-Wesley, 2003.
  5. Esposito, Dino and Saltarello, Andrea. Microsoft® .NET: Architecting Applications for the Enterprise. s.l. : Microsoft Press, 2008.
  6. Nilsson, Jimmy. Applying Domain-Driven Design and Patterns. s.l. : Addison-Wesley, 2008.
  7. Mallalieu, Tim. Initial POCO Design 1-Pager. Entity Framework Design. [Online] Czerwiec 24, 2008. http://blogs.msdn.com/efdesign/archive/2008/06/24/initial-poco-design-1-pager.aspx.
  8. Miller, Jeremy D. It’s time for IoC Container Detente. Jeremy D. Miller — The Shade Tree Developer. [Online] Sierpień 16, 2008. http://codebetter.com/blogs/jeremy.miller/archive/2008/08/16/it-s-time-for-ioc-container-detente.aspx.
  9. Tavares, Chris and Block, Glenn. Common Service Locator. [Online] http://www.codeplex.com/CommonServiceLocator.
  10. Block, Glenn. IServiceLocator a step toward IoC container / Service locator detente. Glenn Block. [Online] Październik 2, 2008. http://codebetter.com/blogs/glenn.block/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx.
  11. Gamma, Erich, et al. Patterns: Elements of Reusable Object-Oriented Software. s.l. : Addison-Wesley, 1995.
VN:F [1.9.13_1145]
Rating: 0.0/5 (0 votes cast)