Wstrzykiwanie zależności
Koziołek
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.
1 Podstawowe informacje
2 Motywacja
3 Rodzaje wstrzyknięć
3.1 Wstrzyknięcie przez konstruktor
3.2 Wstrzyknięcie przez metodę ustawiającą
3.2.1 Wstrzykiwanie przez pola
3.3 Wstrzykiwanie przez interfejs
4 Tworzenie i zarządzanie obiektami
4.4 Wywoływanie konstruktorów
4.5 Wywołanie metody fabrykującej
4.6 Współdzielenie obiektów
5 Podsumowanie
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.
- Java
- Spring Framework - http://www.springsource.org/
- Google Guice - http://code.google.com/p/google-guice/
- PicoContainer - http://picocontainer.org/
- Weld - http://seamframework.org/Weld implementacja referencyjna CDI (JSR-299) - http://jcp.org/en/jsr/detail?id=299
- .NET
- ASP.NET
- Autofac - http://code.google.com/p/autofac/
- php
- Symfony - http://www.symfony-project.org/
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/