Inżynieria oprogramowania

Wstrzykiwanie zależności

Wstrzykiwanie zależności (ang. Dependency Injection - DI) jest rozwiązaniem projektowym, często określanym jako wzorcem projektowym, który pozwala na tworzenie kodu o luźniejszych powiązaniach, łatwiejszego w testowaniu i modyfikacji. Jest implementacją zasady odwrócenia sterowania.



Podstawowe informacje


Głównym założeniem jest przeniesienie tworzenia obiektów oraz wiązania ich między sobą poza kod aplikacji. Obiekty tworzy i wiąże osobna biblioteka nazywana kontener DI. Kontener w celu powiązania obiektów posługuje się konfiguracją, która określa jak obiekty powinny być powiązane.
Obecnie istnieje wiele różnych bibliotek i szkieletów obsługujących wstrzykiwanie zależności.

Motywacja


Jednym z podstawowych założeń programowania obiektowego jest tworzenie kodu w oparciu o kontrakt. Inaczej mówiąc obiekt, którego metody wywołujemy gwarantuje:

  • dobrze określony zestaw parametrów wejściowych.
  • dobrze określone sposoby wyjścia z metody - zwracaną wartość, czy też wyjątek w określonej sytuacji.

Jednocześnie nie musimy zastanawiać się jak dana operacja jest wykonywana. Dzięki temu można określić interfejs klasy, a jego implementację przenieść do osobnych struktur (klas, mixinów itp.). Swoboda implementacji danej metody bez konieczności zmiany kodu zależnego jest jedną z najważniejszych cech programów obiektowych.
Tworząc kod zależny od danego interfejsu musimy samodzielnie utworzyć instancję klasy implementującej ten interfejs. Tym samym nasz kod staje się bardzo mocno związany z konkretną implementacją. Powoduje to, że konieczność zmiany implementacji oznacza dużo zmian w kodzie zależnym. W praktyce tracimy więc wszystkie zalety programowania opartego o kontrakt.

Kolejnym istotnym elementem wpływającym na decyzję o użyciu DI w ramach aplikacji jest łatwość testowania. Jeżeli zamiast tworzyć obiekty będziemy oczekiwać na dostarczenie ich z zewnątrz z założeniem, że dostarczone obiekty spełniają kontrakt to w trakcie rozwoju naszego kodu możemy zamiast konkretnych implementacji posłużyć się imitacjami (ang. mock) obiektów, które będą zachowywać się w określony sposób. W większych projektach oznacza to, że nie ma potrzeby tworzenia całego łańcucha zależności (pamiętajmy, że klasa od której zależymy ma zapewne swoje zależności) w trakcie testów, a wystarczy tylko utworzyć odpowiedni mock. Dodatkowo, jako, że możemy sterować zachowaniem takiego obiektu we w miarę swobodny sposób możemy przetestować sytuacje trudne do uzyskania (np. specyficzne błędy odczytu pliku).

Rodzaje wstrzyknięć


Istnieje wiele różnych sposobów na wstrzyknięcie zależności do obiektu. W tym artykule skupimy się na trzech najpopularniejszych.

Wstrzyknięcie przez konstruktor


Rozwiązanie to jest najprostszym z możliwych. Kontener DI w konfiguracji otrzymuje informację, że obiekty danej klasy należy tworzyć w oparciu o konstruktor posiadający określony zestaw parametrów. Zaletą tego rozwiązania jest prostota. Obiekt po utworzeniu jest gotowy do użycia. Do wad należy przede wszystkim konieczność tworzenia rozbudowanych konstruktorów w przypadku klas operujących na wielu innych klasach (vide kontrolery we wzorcu MVC).
Problematyczne staje się też obsłużenie sytuacji w której kilka klas tworzy cykliczną zależność. Który konstruktor należy wywołać jako pierwszy? W takim wypadku kontenery zazwyczaj tworzą dynamiczne klasy Proxy, które są wstrzykiwane, a dopiero po zakończeniu pracy związanej z wywoływaniem konstruktorów do klas Proxy przekazywane są implementacje.

Wstrzyknięcie przez metodę ustawiającą



Rozwiązanie to opiera się o metody ustawiające, które są wywoływane po utworzeniu obiektu. Zaletą jest łatwość implementacji, odpada problem cyklicznej zależności. Prostota tworzonego kodu, brak konstruktorów o wielu parametrach. Stosunkowo łatwy do określenia przebieg wywołania metod. Najpoważniejszym problemem jest jednak wytworzenie się sytuacji gdzie obiekt pozostaje w stanie "nieustalonym". Po wywołaniu konstruktora, ale przed wywołaniem metod ustawiających. Wtedy łatwo doprowadzić do sytuacji, w której klasy do których wstrzykiwanie następuje przez metody ustawiające są przekazywane do konstruktorów innych klas i tam wywoływane są ich metody co powoduje błąd (zazwyczaj odpowiednik znanego z javy NullPointerException).

Wstrzykiwanie przez pola


Jedną z odmian wstrzykiwania przez metodę ustawiającą jest wstrzykniecie z wykorzystaniem mechanizmów refleksji. W tym przypadku kontener nie wywołuje metody ustawiającej, a na podstawie definicji klasy wstrzykuje obiekty bezpośrednio przypisując je do pól klasy. Zaletą w stosunku do typowego wstrzykiwania przez metodę ustawiającą jest uzyskanie krótszego kodu i eliminacja części metod.

Wstrzykiwanie przez interfejs


W tym rozwiązaniu chcąc uzyskać jakiś obiekt musimy zaimplementować konkretny interfejs. Kontener DI przeszukuje następnie klasy pod kątem tego interfejsu i wstrzykuje zależność wywołując metodę interfejsu. Zaletą jest jasne definiowanie cech klasy poprzez implementację interfejsu. Jednocześnie rozwiązanie to powoduje powstanie znacznych ilości dodatkowego kodu.

Tworzenie i zarządzanie obiektami


Skoro wiemy jak można wstrzyknąć obiekt to zastanówmy się przez chwilę jak kontener DI tworzy obiekty.

Wywoływanie konstruktorów


Kontener DI może wywoływać konstruktor danej klasy z wykorzystaniem mechanizmu refleksji. Jest to najpopularniejsza metoda tworzenia obiektów.

Wywołanie metody fabrykującej


W szczególnych wypadkach zamiast wywoływać konstruktor kontener może przerzucić to wywołanie na metodę fabrykującą. W takim wypadku zadaniem programisty jest dostarczenie klasy - fabryki, która będzie tworzyć obiekty. Jest to szczególnie przydatne rozwiązanie gdy tworzymy obiekty klas o "wysokim ryzyku" np. klientów webservice'ów, chcemy korzystać z puli obiektów np. puli połączeń do bazy danych.

Współdzielenie obiektów


Skoro mowa już o puli obiektów warto zwrócić uwagę na jeszcze jeden aspekt związany z tworzeniem obiektów. Kontener DI pozwala na określenie nie tylko jak będą tworzone obiekty, ale też ile będzie ich tworzonych. W najprostszej wersji kontenery pozwalają na tworzenie nowego obiektu przy każdym wstrzyknięciu albo utworzenie jednej instancji obiektu i traktowanie go jak singleton. To drugie rozwiązanie jest szczególnie przydatne w przypadku gdy nie chcemy samodzielnie tworzyć klasy singletonowej albo użycie singletonu jest zasadne tylko w konkretnym kontekście.
Bardziej zaawansowane kontenery pozwalają na tworzenie obiektów w oparciu o zdefiniowane zasięgi. Zasięgiem może być sesja albo żądanie HTTP, transakcja bazodanowa, transakcja biznesowa (zwana czasami konwersacją) i inne.

Podsumowanie


Wstrzykiwanie zależności jest obecnie najpopularniejszą metodą zarządzania obiektami i ich powiązaniami w aplikacjach. Istnieje wiele różnych kontenerów DI i większość języków obiektowych posiada swoje rozwiązania w tym zakresie. Na koniec wymieńmy najpopularniejsze z nich.

1 komentarz

siararadek 2011-11-26 13:05

Do .NET'a dodaj Autofac, bardzo ciekawy, dający duże możliwości i dosyć prosty w użyciu - http://code.google.com/p/autofac/