Inwersja kontroli kontenerów i wzorzec Dependency Injection


Original: http://martinfowler.com/articles/injection.html
Copyright: Martin Fowler

W środowisku Java nastąpiła rush lekkich pojemników, które pomagają w montażu elementów z różnych projektów w spójną aplikacji. U podstaw tych pojemników jest wspólny wzorzec jak wykonać okablowanie, koncepcji odnoszą się one pod bardzo ogólną nazwą “Inversion of Control”. W tym artykule mam kopać, jak ten wzór działa pod nazwą bardziej szczegółową z “Dependency Injection”, i przeciwstawić ją ewentualnie Locator Service. Wybór między nimi jest mniej ważna niż w konfiguracji zasady rozdzielania z użytkowania.

Jednym z zabawnych rzeczy o Java Enterprise świecie jest ogromna ilość aktywności w budowaniu alternatywy dla nurtu technologii J2EE, wiele z nich dzieje się w open source. Dużo tego jest reakcja na ciężkiej złożoności w nurcie świata J2EE, ale wiele z nich jest także odkrywanie alternatywnych i wymyślanie kreatywnych pomysłów. Powszechnym problemem do czynienia jest to, jak podłączyć razem różne elementy: jak pasują do siebie tę architekturę kontrolera internetowej z tym podkładzie interfejsu bazy danych, gdy zostały one zbudowane przez różne zespoły z niewielkiej wiedzy na temat każdego numeru other.A frameworków podjęły ukłucie ten problem, a kilka z nich rozgałęzienia zapewnienie ogólnego możliwość zmontowania elementów z różnych warstw. Są one często określane jako lekkich pojemników, przykłady obejmują PicoContainer i sprężyny.

U podstaw tych pojemników wiele interesujących zasad projektowania, rzeczy, które wykraczają poza ramy tych pojemników specjalnych, a nawet platformy Java. Tutaj chciałbym rozpocząć odkrywanie niektóre z tych zasad. Przykłady używam są w Javie, ale jak większość z mojego pisania zasady mają również zastosowanie do innych środowisk, zwłaszcza OO. NET.
Komponenty i usługi

Temat elementów okablowania razem ciągnie mnie niemal natychmiast do Knotty problemów terminologicznych, które otaczają usługę Warunki i komponent. Znajdziesz długie i sprzeczne artykuły na temat definicji tych rzeczy z łatwością. Dla moich celów Oto moje aktualne zastosowania tych przeciążonych warunkach.

Używam komponentu oznaczać glob oprogramowania, które jest przeznaczone do użycia, bez zmiany, przez aplikację, która jest poza kontrolą autorów komponentu. Przez “bez zmian” to znaczy, że aplikacja korzystająca nie zmienia kodu źródłowego komponentów, choć mogą zmienić zachowanie składnika rozszerzając go w sposób dozwolony przez pisarzy składowych.

Jest podobne do składnika, ponieważ używany przez zastosowań obcych. Główną różnicą jest to, że oczekuję składnik zostać użyta (myślę plik jar, montaż, dll, lub import źródło). Usługa zostanie wykorzystana zdalnie przez jakiegoś zdalnego interfejsu, zarówno synchroniczne i asynchroniczne (np. usług internetowych, system komunikacji, RPC, lub gniazdo).

I głównie wykorzystywać usługi w tym artykule, ale wiele z samym logiczne mogą być stosowane do lokalnego za składników. Rzeczywiście często trzeba jakąś lokalną ramach komponentu na łatwy dostęp do zdalnej obsługi. Ale pisząc “części składowe lub usługi” jest męczące się czytać i pisać, a także usługi są znacznie bardziej modne w danym momencie.
Naive Przykład

Aby pomóc tym wszystkim bardziej konkretne użyję przykładu uruchomiony, aby porozmawiać o tym wszystkim. Jak każdy z moich przykładach jest to jeden z tych przykładów super-proste, mały, aby być nierealne, ale miejmy nadzieję, wystarczy, aby uzmysłowić, co dzieje się bez popadania w bagnie prawdziwy przykład.

W tym przykładzie Piszę składnik, który zapewnia listę filmów wyreżyserowanych przez danego dyrektora. Ta niezwykle przydatna funkcja jest realizowana przez jednego sposobu.

Klasa MovieLister …
public Movie [] moviesDirectedBy (String arg) {
AllMovies Lista = finder.findAll ();
for (Iterator it = allMovies.iterator (); it.hasNext () ;) {
Movie = Movie (Film) it.next ();
jeśli it.remove () (movie.getDirector () równa się (arg)!.);
}
return (Movie []) allMovies.toArray (nowy Movie [allMovies.size ()]);
}

Realizacja tej funkcji jest naiwny w ekstremalnych, prosi obiekt wizjera (który dostaniemy w chwilę), aby powrócić wszystkie filmy to wie. Wtedy to właśnie poluje po tej liście, aby powrócić tych skierowanych przez danego dyrektora. Ten konkretny kawałek naiwności nie zamierzam naprawić, ponieważ jest to tylko rusztowanie dla prawdziwego punktu tego artykułu.

Prawdziwy punkt tego artykułu jest to obiekt wyszukiwarki, lub szczególnie jak połączyć obiekt Lister z danego obiektu Finder. Powodem jest to interesujące jest, że chcę mój wspaniały sposób moviesDirectedBy być całkowicie niezależny od jak wszystkie filmy są magazynowaniem. Więc wszystko jest metoda nie odnosi się do wyszukiwarki i wszystko, co nie jest znane wyszukiwarki, jak reagować na metody findall. Mogę zabrać to od zdefiniowania interfejsu dla znalazcy.

public MovieFinder interfejs {
Listy findall ();
}

Teraz to wszystko jest bardzo dobrze oddzielone od produkcji, ale w pewnym momencie będę musiał pochodzić z betonu klasy rzeczywiście pochodzić z filmów. W tym przypadku mogę umieścić kod na to w konstruktorze mojej klasie Lister.

Klasa MovieLister …
prywatny MovieFinder finder;
MovieLister publicznych () {
finder = new ColonDelimitedMovieFinder (“movies1.txt”);
}

Nazwa klasy realizacji wynika z faktu, że jestem coraz moje listy z rozdzielany grubego pliku tekstowego. Ja oszczędzę szczegółów, po wszystkim chodzi o to tylko, że jest jakaś implementacja.

Teraz, jeśli używam tej klasy tylko dla siebie, to wszystko jest cacy. Ale co się dzieje, gdy moi przyjaciele są przytłoczeni pragnienia tej wspaniałej funkcjonalności i chciałby kopię mojego programu? Jeśli również przechowywać swoje oferty filmowe w okrężnicy rozdzielany plik tekstowy o nazwie “movies1.txt” to wszystko jest wspaniałe. Jeżeli mają inną nazwę dla swojego archiwum filmów, wtedy łatwo jest umieścić nazwę pliku w pliku właściwości. Ale co, jeśli oni mają zupełnie inną formę przechowywania ich lista film: bazy danych SQL, plik XML, usługi sieci Web, lub po prostu inny format pliku tekstowego? W tym przypadku musimy inną klasę złapać tych danych. Teraz, bo już zdefiniowany interfejs MovieFinder, to nie zmieni mojego moviesDirectedBy metody. Ale jeszcze trzeba mieć jakiś sposób na zdobycie instancję prawym realizacji znajdź swoje miejsce.

Figure 1

Rysunek 1: Zależności wykorzystujące proste tworzenie w klasie Lister

Rysunek 1 przedstawia zależności dla tej sytuacji. Klasa MovieLister zależy zarówno interfejsu MovieFinder i na życie. Wolelibyśmy, gdyby to było tylko zależna od interfejsu, ale to w jaki sposób utworzyć instancję pracować?

W moim P księgowej EAA opisaliśmy tę sytuację jako Plugin. Klasa wdrożenie dla znalazcy nie jest związane z programem w czasie kompilacji, ponieważ nie wiem, co moi przyjaciele będą korzystać. Zamiast tego, żeby mój obsypnik do pracy z dowolnym realizacji, a do tego wdrożenia jest podłączony w późniejszym momencie, z moich rąk. Problem jest w jaki sposób można sprawić, że związek tak, że moja klasa Lister jest ignorantem klasy wykonania, ale może mówić do instancji robić swoją pracę.

Rozszerzenie to do rzeczywistego systemu, możemy mieć kilkadziesiąt takich usług i komponentów. W każdym przypadku możemy streszczenie naszego użytku tych składników przez rozmawiając z nimi za pomocą interfejsu (i przy użyciu zasilacza, jeśli składnik nie został zaprojektowany z myślą o interfejsie). Ale jeśli chcemy wdrożyć ten system w inny sposób, musimy użyć wtyczki do obsługi interakcji z tych usług, więc możemy korzystać różne implementacje w różnych wdrożeń.

Tak więc głównym problemem jest to, jak możemy zebrać te wtyczki do aplikacji? Jest to jeden z głównych problemów, że to nowy gatunek lekkiego twarzy pojemników, i powszechnie wszyscy zrobić to za pomocą Inwersja Control.

Inversion of Control

Kiedy te pojemniki mówić o tym jak oni są tak użyteczne, ponieważ wdrożenie “Inwersja Control” I kończy się bardzo zdziwiony. Inwersja kontroli jest wspólną cechą ram, więc powiedzieć, że te lekkie pojemniki są specjalne, ponieważ korzystają one odwrócenie kontroli, jest jak mówienie, mój samochód jest wyjątkowy, ponieważ jest na kółkach.

Pytanie, co aspekt kontroli są one odwrócenie? Kiedy po raz pierwszy spotkałem inwersji kontroli, to w panelu sterowania interfejsem użytkownika. Wczesne interfejsy użytkownika są kontrolowane przez aplikację. To masz sekwencję poleceń jak “Podaj nazwę”, “podaj adres”; program będzie prowadzić monity i odebrać odpowiedź do każdego z nich. Z graficznym (lub nawet ekran based) Uis ramy UI będzie zawierać tę główną pętlę i Twój program zamiast przewidzianej obsługi zdarzeń dla różnych dziedzin na ekranie. Głównym kontroli programu został odwrócony, odszedł od ciebie do ram.

Dla tej nowej rasy kontenerów inwersja jest o tym, jak wyszukiwanie implementacji wtyczki. W mojej naiwnej przykład lister spojrzał realizację znajdź bezpośrednio instancji go. To zatrzymuje Finder z bycia plugin. Podejście, że to wykorzystanie pojemników jest zapewnienie, że każdy użytkownik plugin następuje pewne konwencje, które pozwala oddzielny moduł assembler wstrzyknąć wdrażanie do Lister.

W rezultacie uważam, że musimy bardziej konkretną nazwę dla tego wzoru. Inversion of Control jest zbyt ogólne określenie, a więc ludzie uważają, że mylące. W związku z dużą ilością dyskusji z różnych adwokatów MKOl zdecydowaliśmy się na Dependency Injection nazwy.

Mam zamiar zacząć od rozmowy o różnych formach Dependency Injection, ale będę teraz zwrócić uwagę, że nie jest to jedyny sposób na usunięcie zależności od klasy aplikacji do realizacji wtyczek. Inny wzór można użyć do tego jest obsługa sklepów, a ja o tym rozmawiać po Skończyłem z wyjaśniając Dependency Injection.
Formy Dependency Injection

Podstawową ideą Dependency Injection jest mieć oddzielny obiekt, Assembler, że zapełnia pole w klasie Lister z odpowiednim realizacji dla interfejsu znajdź, co na diagramie zależności wzdłuż linii rysunku 2

Figure 2

Rysunek 2: Zależności dla Dependency Injector

Istnieją trzy główne style Dependency Injection. Nazwy używam dla nich są konstruktora wtrysku, wtrysk Setter i wtrysku Interface. Jeśli czytasz o tych rzeczach w bieżących dyskusjach o Inversion Kontroli usłyszysz te określane jako typ 1 IoC (wstrzyknięcie interface), typu 2 IOC (wstrzyknięcie rozgrywający) i typu 3 IOC (wstrzyknięcie konstruktora). Uważam numeryczne nazwy raczej trudne do zapamiętania, dlatego użyłem nazwy mam tutaj.
Wtrysk Konstruktor z PicoContainer

Zacznę pokazując, jak to odbywa się za pomocą wtrysku lekki pojemnik zwany PicoContainer. Zaczynam się tutaj przede wszystkim dlatego, kilku moich kolegów ThoughtWorks są bardzo aktywne w rozwoju PicoContainer (tak, to jest coś w rodzaju przedsiębiorstwa nepotyzm).

PicoContainer używa konstruktora do decydowania jak wstrzykiwać implementację znajdź w klasie Lister. Aby to zadziałało, klasa Lister film musi zadeklarować konstruktora, który zawiera wszystko, co potrzebne do wstrzyknięcia.

Klasa MovieLister …
public MovieLister (MovieFinder finder) {
this.finder = finder;
}

Wyszukiwarka sama będzie również zarządzane przez kontener pico, i jako taki będzie miał nazwę pliku tekstowego wtryskiwanego do niego zbiornika.

Klasa ColonMovieFinder …
public ColonMovieFinder (string nazwa_pliku) {
this.filename = nazwa pliku;
}

Pojemnik pico Następnie należy stwierdzić, która klasa realizacja skojarzyć z każdego interfejsu, który ciąg znaków i wprowadzić do znalazcy.

prywatny MutablePicoContainer configureContainer () {
MutablePicoContainer pico = new DefaultPicoContainer ();
Parametr [] = {new finderParams ConstantParameter (“movies1.txt”);}
pico.registerComponentImplementation (MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation (MovieLister.class);
powrót pico;
}

Ten kod konfiguracji zazwyczaj konfiguruje się w innej klasie. W naszym przykładzie, każdy przyjaciel, który używa mojego Lister może napisać odpowiedni kod konfiguracji w jakiejś klasie ustawień własnych. Oczywiście, że to wspólna trzymać tego rodzaju informacji konfiguracyjnych w oddzielnych plikach konfiguracyjnych. Możesz napisać klasę do czytania pliku konfiguracyjnego i ustawić pojemnik odpowiednio. Chociaż PicoContainer nie zawiera tej funkcji, sama, jest ściśle związany z projektem o nazwie NanoContainer który zapewnia odpowiednie obwolut do pozwalają mieć XML pliki konfiguracyjne. Taki pojemnik nano będzie analizować plik XML, a następnie skonfigurować podstawowej pojemnik pico. Filozofia projektu jest oddzielenie konfiguracji format pliku z podstawowego mechanizmu.

Aby korzystać z pojemnika coś napisać kod jak poniżej.

public testWithPico void () {
Pico MutablePicoContainer = configureContainer ();
MovieLister lister = (MovieLister) pico.getComponentInstance (MovieLister.class);
Movie [] = lister.moviesDirectedBy filmy (“Sergio Leone”);
assertEquals (“Once Upon a Time in the West”, filmy [0] getTitle ().);
}

Chociaż w tym przykładzie użyłem konstruktora wtrysku, PicoContainer obsługuje również zastrzyk setter, choć jego twórcy nie wolą konstruktora wtrysku.
Wtrysk Setter z wiosny

Spring Framework jest szeroko zakrojone ramy dla Enterprise Java rozwoju. Obejmuje on warstwy abstrakcji dla transakcji, ram wytrwałość, tworzenie aplikacji internetowych i JDBC. Jak PicoContainer obsługuje zarówno konstruktora i wtrysk setter, ale jego twórcy wolą zastrzyk setter – co sprawia, że ​​jest to odpowiedni wybór dla tego przykładu.

Aby mój film Lister przyjąć zastrzyk zdefiniować metodę ustawienia tej usługi

Klasa MovieLister …
prywatny MovieFinder finder;
public void setFinder (MovieFinder finder) {
this.finder = finder;
}

Podobnie I zdefiniować setter do nazwy pliku.

Klasa ColonMovieFinder …
public void setFilename (string nazwa_pliku) {
this.filename = nazwa pliku;
}

Trzecim krokiem jest utworzenie konfiguracji dla plików. Wiosna obsługuje konfigurację poprzez pliki XML, a także za pomocą kodu, ale XML jest oczekiwany sposób to zrobić.

“><beans>
“><bean id=”MovieLister”>
“><property name=”finder”>
“><ref local=”MovieFinder”/>
“></ Property>
“></ Bean>
“><bean id=”MovieFinder”>
“><property name=”filename”>
movies1.txt”><wartość> movies1.txt </ value>
“></ Property>
“></ Bean>
“></ Fasola>

Test potem wygląda.

public void testWithSpring () throws Exception {
ApplicationContext ctx = new FileSystemXmlApplicationContext (“spring.xml”);
MovieLister lister = (MovieLister) ctx.getBean (“MovieLister”);
Movie [] = lister.moviesDirectedBy filmy (“Sergio Leone”);
assertEquals (“Once Upon a Time in the West”, filmy [0] getTitle ().);
}

Wtrysk Interfejs

Trzeci jest iniekcji i określenie wykorzystania interfejsy do wstrzyknięcia. Avalon przykład ramach tej techniki, że używa się w miejscu. Odezwę się nieco więcej o tym później, ale w tym przypadku mam zamiar używać go z jakimś prostym przykładowego kodu.

Dzięki tej technice mogę zacząć od zdefiniowania interfejsu, który będziesz używać do wykonywania iniekcji przez. Oto interfejs do wstrzykiwania Movie Finder na obiekt.

public InjectFinder interfejs {
void injectFinder (MovieFinder finder);
}

Interfejs ten będzie określać, kto udostępnia interfejs MovieFinder. To musi być realizowane przez wszystkie klasy, które chce użyć wyszukiwarki, takie jak Lister.

Klasa MovieLister wdraża InjectFinder …
public void injectFinder (MovieFinder finder) {
this.finder = finder;
}

Używam podobnego podejścia do wstrzyknięcia pliku do wykonania Finder.

public InjectFinderFilename interfejs {
injectFilename void (String filename);
}

Klasa ColonMovieFinder wdraża MovieFinder, InjectFinderFilename ……
public void injectFilename (string nazwa_pliku) {
this.filename = nazwa pliku;
}

Potem, jak zwykle, potrzebuję kodu konfiguracji, aby połączyć się implementacje. Dla uproszczenia zrobię to w kodzie.

Tester klasy …
prywatny Container;

private void configureContainer () {
pojemnik = Container new ();
registerComponents ();
registerInjectors ();
container.start ();
}

Taka konfiguracja ma dwa etapy, rejestrację składników przez odnośników klawiszy jest bardzo podobny do innych przykładów.

Tester klasy …
prywatne registerComponents void () {
container.registerComponent (“MovieLister” MovieLister.class);
container.registerComponent (“MovieFinder” ColonMovieFinder.class);
}

Nowy krokiem jest rejestracja wtryskiwaczy że wstrzyknie zależne komponenty. Każdy interfejs wtrysk potrzebuje kod do wstrzyknąć utrzymaniu obiektu. Tutaj to zrobić, rejestrując obiekty wtryskiwaczy wraz z kontenerem. Każdy obiekt implementuje interfejs wtryskiwacz wtryskiwacza.

Tester klasy …
prywatne registerInjectors void () {
container.registerInjector (InjectFinder.class, container.lookup (“MovieFinder”));
container.registerInjector (InjectFinderFilename.class, nowy FinderFilenameInjector ());
}

public interface Injector {
public void wstrzyknięcia (Object target);

}

Kiedy zależy to klasa napisana dla tego kontenera, ma sens dla składnika do realizacji interfejsu wtryskiwacza sama, jak ja tutaj z wyszukiwarki filmów. Do ogólnych klas, np. łańcucha, użyć klasy wewnętrznej konfiguracji w kod.

Klasa ColonMovieFinder wdraża wtryskiwacz ……
public void wstrzyknąć (target Object) {
. ((InjectFinder) target) InjectFinder (this);
}

Tester klasy …
public static class FinderFilenameInjector wdraża wtryskiwacz {
public void wstrzyknąć (target Object) {
((InjectFinderFilename) target) InjectFilename (“movies1.txt.”);
}
}

Następnie za pomocą badania pojemnika.

Klasa IfaceTester …
public testIface void () {
configureContainer ();
MovieLister lister = (MovieLister) container.lookup (“MovieLister”);
Movie [] = lister.moviesDirectedBy filmy (“Sergio Leone”);
assertEquals (“Once Upon a Time in the West”, filmy [0] getTitle ().);
}

Kontener używa zadeklarowanych interfejsy wtryskowych, aby dowiedzieć się zależności i wtryskiwaczy do wstrzyknięcia odpowiedniej żywiciela. (Realizacja konkretnych kontenerów zrobiłem tutaj nie jest ważne do techniki, i nie pokaże, bo chcesz tylko śmiać.)
Korzystanie z usługi lokalizatora

Podstawową zaletą jest to, że Dependency Injector usuwa zależność, że klasa MovieLister ma na betonowej realizacji MovieFinder. To pozwala mi dać obsypniki do znajomych i dla nich, aby podłączyć do odpowiedniego wdrożenia dla własnego środowiska. Wtrysk nie jest jedynym sposobem na przełamanie tej zależności, inny jest skorzystać z usługi lokalizatora.

Podstawową ideą usługę lokalizatora jest mieć obiekt, który wie, jak zdobyć wszystkie usługi, które aplikacja może potrzebować. Więc lokalizatora usług dla tej aplikacji nie ma metody, która zwraca Movie Finder gdy jest potrzebna. Oczywiście to tylko przesuwa ciężar odrobinę, nadal mamy dostać lokalizator do Lister, co w zależności od rysunku 3

Figure 3

Rysunek 3: Zależności dla usługi lokalizatora

W tym przypadku użyję ServiceLocator jako singleton sekretariacie. Obsypnik może użyć, że aby uzyskać wizjera, kiedy to instancja.

Klasa MovieLister …
MovieFinder finder = ServiceLocator.movieFinder ();

ServiceLocator class …
public static movieFinder MovieFinder () {
powrót soleInstance.movieFinder;
}
private static ServiceLocator soleInstance;
prywatny MovieFinder movieFinder;

Zgodnie z podejściem wtrysku, musimy skonfigurować usługę lokalizatora. Tutaj robię to w kodzie, ale to nie jest trudne do zastosowania mechanizmu, który odczytać odpowiednie dane z pliku konfiguracyjnego.

Tester klasy …
private void configure () {
ServiceLocator.load (nowy ServiceLocator (nowy ColonMovieFinder (“movies1.txt”)));
}

ServiceLocator class …
public static void load (ServiceLocator arg) {
soleInstance = arg;
}

public ServiceLocator (MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}

Oto kod test.

Tester klasy …
public testSimple void () {
configure ();
MovieLister lister = new MovieLister ();
Movie [] = lister.moviesDirectedBy filmy (“Sergio Leone”);
assertEquals (“Once Upon a Time in the West”, filmy [0] getTitle ().);
}

Często słyszałem zarzut, że tego rodzaju usługi lokalizatory są złe, bo nie są sprawdzalne, ponieważ nie można zastąpić implementacje dla nich. Oczywiście można zaprojektować je źle dostać się do tego rodzaju kłopoty, ale nie muszą. W tym przypadku usługa instancji lokator jest tylko prostą posiadaczem danych. I można łatwo utworzyć lokalizatora z wdrożeń testowych moich usług.

Dla bardziej zaawansowanych lokalizatora mogę podklasy lokalizatora usług i przekazać tę podklasę do rejestru do zmiennej klasy. Mogę zmienić statyczne metody do wywołania metody na przykład raczej dostęp zmiennych instancji bezpośrednio. Mogę zapewnić nici konkretne lokalizatory za pomocą gwintu konkretnego magazynu. Wszystko to można zrobić bez zmiany klientom usługi lokalizatora.

Sposób myślenia jest to, że usługa jest lokalizator rejestru nie singleton. Singleton zapewnia prosty sposób wdrożenie rejestru, ale decyzja ta implementacja jest bardzo prosta.
Korzystanie z interfejsu na segregowane Locator

Jednym z problemów, z prostego podejścia powyżej, jest to, że w zależności od MovieLister jest pełne usługi lokalizatora klasy, chociaż wykorzystuje się tylko jedną usługę. Możemy zmniejszyć to za pomocą wydzielonych interfejs. W ten sposób, zamiast korzystać z pełnej obsługi lokalizatora interfejs, obsypnik może zadeklarować tylko trochę interfejs potrzebuje.

W tej sytuacji dostawca Lister zapewni również interfejs lokalizatora których potrzebuje zdobyć znalazcy.

public MovieFinderLocator interfejs {
public MovieFinder movieFinder ();

Lokator musi następnie implementuje ten interfejs, aby zapewnić dostęp do znalazcy.

MovieFinderLocator lokator = ServiceLocator.locator ();
MovieFinder finder = locator.movieFinder ();

public static ServiceLocator lokator () {
powrót soleInstance;
}
public MovieFinder movieFinder () {
powrót movieFinder;
}
private static ServiceLocator soleInstance;
prywatny MovieFinder movieFinder;

Można zauważyć, że ponieważ chcemy użyć interfejsu, nie możemy po prostu przejść do usługi poprzez metody statyczne więcej. Musimy korzystać z klasy, aby uzyskać instancję lokalizatora, a następnie użyć, że aby uzyskać to, czego potrzebujemy.
Dynamiczna obsługa Locator

Powyższy przykład był statyczny, w tym usługa lokalizatora klasa ma metody do każdej z usług, które potrzebujesz. To nie jest jedyny sposób to zrobić, można również dokonać dynamicznej usługi lokalizatora, który pozwala schować wszelkie usługi potrzebne do niego i dokonać wyboru przy starcie.

W tym przypadku usługa lokator używa mapę zamiast pól dla każdej z usług i zapewnia różne metody, aby uzyskać i załadować usługi.

ServiceLocator class …
private static ServiceLocator soleInstance;
public static void load (ServiceLocator arg) {
soleInstance = arg;
}
prywatne usługi Map = new HashMap ();
public static getService Object (String key) {
powrót soleInstance.services.get (key);
}
public void loadService (key, String obsługa Object) {
services.put (klucz, usługi);
}

Konfiguracja obejmuje załadunek usługi z odpowiednim kluczem.

Tester klasy …
private void configure () {
ServiceLocator lokator = ServiceLocator new ();
locator.loadService (“MovieFinder”, new ColonMovieFinder (“movies1.txt”));
ServiceLocator.load (lokator);
}

I korzystać z usługi za pomocą tego samego klucza ciąg.

Klasa MovieLister …
MovieFinder finder = (MovieFinder) ServiceLocator.getService (“MovieFinder”);

Na ogół nie lubię takiego podejścia. Mimo że jest to na pewno elastyczna, to nie jest bardzo wyraźne. Tylko w ten sposób mogę się dowiedzieć, w jaki sposób dotrzeć do serwisu jest poprzez tekstowych przycisków. Wolę jawnych metod, ponieważ jest łatwiejsze do znalezienia, gdzie są one patrząc na definicjach interfejsów.
Korzystanie zarówno lokator i wtrysku z Avalonu

Dependency Injection i lokalizatora usług nie muszą się wzajemnie wykluczać. Dobrym przykładem wykorzystania zarówno razem stanowi ramy Avalon. Avalon używa lokalizatora usług, ale używa zastrzyku powiedzieć komponenty gdzie znaleźć lokator.

Berin Loritsch wysłał mi tę prostą wersję moim przykładzie działa przy użyciu Avalon.

public MyMovieLister klasa implementuje MovieLister, Serviceable {
prywatny MovieFinder finder;

usług publicznych void (ServiceManager manager) rzuca ServiceException {
finder = (MovieFinder) manager.lookup (“znajdź”);
}

Obsługa jest metodą wtrysku przykład interfejsu umożliwia opakowanie wstrzyknąć do MyMovieLister menedżera usług. Service Manager jest przykładem usługi lokalizatora. W tym przykładzie Lister nie przechowuje menedżera w polu, zamiast natychmiast używa go do wyszukiwania wizjera, co robi, sklep.
Podejmowanie decyzji, który możliwość korzystania

Jak dotąd koncentrowały się na wyjaśnieniu jak widzę te wzory i ich odmian. Teraz mogę zacząć mówić o ich zaletach i wadach, aby pomóc dowiedzieć się, które z nich korzystać i kiedy.
Obsługa Locator Dependency Injection vs

Podstawowym wyborem jest między lokalizatora usług i Dependency Injection. Pierwszym punktem jest to, że obie implementacje zapewnić podstawową oddzielenie że brakuje w naiwnej przykład – w obu przypadkach kod aplikacji jest niezależny od konkretnej implementacji interfejsu serwisowego. Istotna różnica pomiędzy dwóch wzorów, jak to jest, że wprowadzenie do klasy aplikacji. Z usługi lokalizatora klasa aplikacja zażąda wyraźnie przez wiadomości do lokalizatora. Z wtryskiem nie ma wyraźnego żądania, usługa pojawi się w klasie aplikacji – stąd odwrócenie kontroli.

Inwersja kontroli jest wspólną cechą ram, ale to jest coś, co jest w cenie. To wydaje się być trudne do zrozumienia i prowadzi do problemów, kiedy próbuje do debugowania. Więc na ogół wolę go unikać, chyba że jest to potrzebne. To nie znaczy, że to jest złe, tak że myślę, że to musi usprawiedliwiać się nad alternatywą łatwiejszy.

Podstawową różnicą jest to, że z usług Locator każdy użytkownik usługi ma zależność do lokalizatora. Lokator może ukryć zależności do innych implementacji, ale trzeba zobaczyć lokator. Więc decyzja między lokalizatora a wtryskiwaczem zależy od tego, czy uzależnienie jest problemem.

Korzystanie iniekcji zależność pomaga łatwiej zobaczyć, co zależności składowe są. Z zależności wtryskiwacza można po prostu spojrzeć na mechanizm wtrysku, takich jak konstruktora i zobaczyć zależności. Dzięki usłudze lokalizatora trzeba szukać kodu źródłowego za połączenia do lokalizatora. Nowoczesna IDE z funkcji Znajdź referencje to ułatwić, ale to nadal nie jest tak łatwe, jak patrząc na konstruktora lub ustawienie metod.

Wiele z tego zależy od charakteru użytkownika usługi. Jeśli tworzysz aplikację z różnych klas, które używają usługi, wtedy zależność od klas aplikacji do lokalizatora nie jest wielka sprawa. W moim przykładzie dając Lister filmu do moich przyjaciół, a następnie za pomocą lokalizatora usług działa całkiem dobrze. Wszystko, co musisz zrobić, to skonfigurować lokator podłączyć w odpowiednich implementacji usług, albo przez jakiegoś kodu konfiguracji lub za pośrednictwem pliku konfiguracyjnego. W tego rodzaju sytuacji nie widzę inwersji wtryskiwacz jako zapewnienie nic przekonujące.

Różnica wynika jeśli Lister jest składnikiem, że jestem dostarczanie do wniosku, że inni ludzie są pisania. W tym przypadku nie wiem zbyt wiele o API z lokalizatorów serwisowych, że moi klienci będą korzystać. Każdy klient może mieć swoje własne niezgodnych lokalizatory usług. Można uzyskać wokół niektórych to za pomocą oddzielnej interfejs. Każdy klient może napisać adapter, który pasuje do ich mój interfejs lokalizator, ale w każdym razie muszę jeszcze zobaczyć pierwszy lokalizator do wyszukiwania moje określony interfejs. A kiedy pojawia się wówczas adapter prostota bezpośredniego podłączenia lokalizatora zaczyna się ślizgać.

Ponieważ z wtryskiwacza nie są zależne od elementu do wtryskiwacza, element nie może uzyskać dodatkowe usługi z wtryskiwacza po jego skonfigurowane.

Częstą przyczyną ludzie dają do wstrzykiwań preferuje zależności jest to, że sprawia, że ​​testowanie łatwiejsze. Chodzi o to, że aby wykonać test, trzeba łatwo zastąpić rzeczywiste wdrożenia usług z łącznikami lub drwi. Jednak tak naprawdę nie ma różnicy tutaj między zależność wtrysku i usług lokator: oba są bardzo podatne na stubbing. I podejrzewam, że obserwacja pochodzi z projektów, w których ludzie nie czynią starań, aby ich obsługa lokator można łatwo zastąpić. To jest, gdzie ciągłe testowanie pomaga, jeśli nie można łatwo usługi pośredniczące do testów, to oznacza poważny problem z projektu.

Oczywiście problem testowania pogarsza środowiskach składowych, które są bardzo inwazyjne, takie jak ramy EJB Java. Uważam, że tego rodzaju ramy powinny zminimalizować ich wpływ na kod aplikacji, a szczególnie nie należy robić rzeczy, które spowalniają edytować-wykonać cykl. Używanie wtyczek zastąpić masywny komponenty robi wiele, aby wspomagać ten proces, który jest niezbędny do praktyk, takich jak Test Driven Development.

Tak więc głównym problemem jest dla ludzi, którzy piszą kod, który oczekuje, że będzie używany w zastosowaniach poza kontrolą pisarza. W tych przypadkach, nawet minimalne założenie o usługi lokalizatora jest problem.
Konstruktor Setter wtryskowa kontra

Dla połączenia usług, zawsze musisz mieć jakieś Konwencję w celu drut rzeczy. Zaletą wtrysku jest przede wszystkim to, że wymaga bardzo prostych konwencje – przynajmniej dla konstruktora i zastrzyki setter. Nie musisz robić nic dziwnego w części i to jest dość oczywiste dla wtryskiwacza, aby wszystko skonfigurowane.

Wtrysk interfejs jest bardziej inwazyjny, ponieważ trzeba napisać dużo interfejsów dostać rzeczy wszystko załatwione. Dla małej grupy interfejsów wymaganych pojemnika, takich jak w podejściu Avalon, nie jest tak źle. Ale to jest dużo pracy do montażu komponentów i zależności, dlatego prąd Wole lekkich pojemnikach iść z setter i wtrysku konstruktora.

Wybór pomiędzy setter i wtrysku konstruktora jest interesujący, gdyż odzwierciedla w sposób bardziej ogólny problem z programowania obiektowego – należy wypełnić pola w konstruktorze lub ustawiaczy.

Moja długo działa domyślnie z obiektów jest jak najbardziej, do tworzenia ważnych obiektów w czasie budowy. Ta rada sięga Smalltalk Kenta Becka najlepszych wzorów praktyka: metoda konstruktora i Method Parameter Constructor. Konstruktorzy z parametrami daje jasne określenie, co to znaczy utworzyć prawidłowy obiekt w widocznym miejscu. Jeśli istnieje więcej niż jeden sposób, aby to zrobić, należy utworzyć wiele konstruktorów, które pokazują różne kombinacje.

Kolejną zaletą jest to, że inicjalizacji konstruktora pozwala na wyraźnie ukryć pola, które są niezmienne, po prostu nie zapewniając setter. Myślę, że to jest ważne – jeśli coś nie powinno się zmienić, a następnie brak setter komunikuje to bardzo dobrze. Jeśli używasz ustawiające dla inicjowania, to może stać się ból. (Rzeczywiście, w takich sytuacjach wolę unikać zwykłej konwencji ustawień, wolę metodę jak initFoo, aby podkreślić, że jest to coś, co należy zrobić tylko w momencie urodzenia.)

Ale z każdej sytuacji są wyjątki. Jeśli masz dużo parametrów konstruktora rzeczy mogą wyglądać niechlujnie, szczególnie w językach bez parametrów kluczowych. To prawda, że ​​długo konstruktor jest często oznaką nadmiernie ruchliwego obiektu, które powinny być rozdzielone, ale są przypadki, że to, czego potrzebujesz.

Jeśli masz wiele sposobów do tworzenia ważnego obiektu, to może być trudne, aby zobaczyć to przez konstruktorów, ponieważ konstruktorzy mogą różnić się w zależności od liczby i rodzaju parametrów. To jest, gdy metody fabryczne wchodzą w grę, to można używać kombinacji prywatnych konstruktorów i ustawiających do wdrożenia ich pracę. Problem z klasycznych metod fabrycznych dla komponentów zespołu jest to, że zwykle są one postrzegane jako metody statyczne, a nie można mieć tych interfejsów. Można zrobić klasy fabrycznej, ale wtedy to po prostu staje się inny przykład obsługa. Serwis fabryczny jest często dobra taktyka, ale wciąż masz instancję fabryki przy użyciu jednej z technik tutaj.

Konstruktorzy również cierpią, jeśli masz proste parametry, takie jak ciągi. Z wtryskiem setter można nadać każdej Setter nazwę wskazania co ciąg jest robić. Z konstruktorami jesteś tylko opierając się na stanowisku, które jest trudniejsze do naśladowania.

Jeśli masz wiele konstruktorów i dziedziczenia, co można uzyskać szczególnie kłopotliwe. Aby zainicjować wszystko, co masz do zapewnienia konstruktorów do przekazania każdego konstruktora nadklasy, a także dodanie Ci własnych argumentów. To może prowadzić do jeszcze większego wybuchu konstruktorów.

Pomimo wad moje preferencje jest zacząć z wtryskiem konstruktora, ale być gotowy, aby przejść do wstrzykiwań setter, jak tylko mam problemów opisanych powyżej zaczynają się problem.

Kwestia ta doprowadziła do wielu dyskusji pomiędzy różnymi zespołami, które dostarczają wtryskiwacze zależności w ramach swoich struktur. Jednak wydaje się, że większość ludzi, którzy budują te szkielety sobie sprawę, że ważne jest, aby wspierać zarówno mechanizmy, nawet jeśli jest preferowanie jednego z nich.
Kod lub plików konfiguracyjnych

Osobno, ale często łączyła kwestią jest, czy korzystać z plików konfiguracyjnych lub kodu na API drut usług. Dla większości aplikacji, które mogą być rozmieszczone w wielu miejscach, oddzielny plik konfiguracyjny zazwyczaj sprawia najwięcej sensu. Prawie cały czas będzie to plik XML, a to ma sens. Istnieją jednak przypadki, w których łatwiej jest używać kodu programu do wykonania montażu. Jedna sprawa jest, gdy masz prosty wniosek, że nie ma dużo zmienności rozmieszczenia. W tym przypadku część kodu może być wyraźniejszy niż w osobnym pliku XML.

Kontrastowe przypadek gdzie montaż jest dość skomplikowane, obejmujące warunkowych czynności. Po uruchomieniu zbliża się do języka programowania, a następnie zaczyna się uszkodzi XML i lepiej jest użyć prawdziwego języka, który posiada wszystkie składni dodać jasny program. Następnie napisać klasę budowniczego, który wykonuje zespół. Jeśli masz odmienne scenariusze budowniczy możesz podać kilka klas Builder i użycie prostego pliku konfiguracyjnego, aby wybrać pomiędzy nimi.

Często myślę, że ludzie są zbyt chętni do definiowania plików konfiguracyjnych. Często język programowania umożliwia prosty i potężny mechanizm konfiguracji. Nowoczesne języki można łatwo skompilować małych asemblerów które mogą być używane do montażu wtyczek do większych systemów. Jeśli kompilacja jest ból, to nie są językach skryptowych, które działają dobrze również.

Często mówi się, że pliki konfiguracyjne nie należy używać język programowania, ponieważ muszą być edytowane przez nie-programistów. Ale jak często jest to przypadek? Czy ludzie naprawdę oczekują nie-programistów do zmiany poziomów izolacji transakcji złożonego po stronie serwera aplikacji? Non-językowe pliki konfiguracyjne działa dobrze tylko w zakresie, w jakim są proste. Jeśli stają się skomplikowane a potem nadszedł czas, aby myśleć o użyciu odpowiedniego języka programowania.

Jedną z rzeczy, którego jesteśmy świadkami w świecie Java w tej chwili jest kakofonia plików konfiguracyjnych, gdzie każdy element ma swoje własne pliki konfiguracyjne, które różnią się od pozostałych. Jeśli korzystasz z tuzin tych elementów, można łatwo skończyć z kilkunastu plików konfiguracyjnych, aby utrzymać synchronizację.

Moja rada jest tutaj zawsze podać sposób w całej konfiguracji łatwo z programowego interfejsu, a następnie leczenia oddzielnego pliku konfiguracyjnego jako wyposażenie opcjonalne. Można łatwo zbudować plik konfiguracyjny obsługi używać interfejs programistyczny. Jeśli piszesz komponent następnie pozostawić do danego użytkownika, czy używać interfejs programistyczny, format pliku konfiguracyjnego, lub napisać swój własny niestandardowy format pliku konfiguracyjnego i związać je w interfejs programistyczny
Oddzielenie Konfiguracja z użytkowania

Ważnym problemem w wszystko to w celu zapewnienia, że ​​w konfiguracji usług jest oddzielone od ich stosowania. W rzeczywistości jest to podstawowa zasada projektowania, który siedzi z wydzieleniem interfejsy od ich wprowadzenia. To coś, co zobaczymy w ramach obiektowego programu podczas warunkowego logiki decyduje, która klasa do instancji, a następnie w przyszłości oceny, które uzależnione są realizowane przez polimorfizmu, a nie poprzez dublowanie warunkowego kodu.

Ile takie oddzielenie jest przydatne w pojedynczej bazy kodu, jest to szczególnie ważne, gdy używasz obcych elementów, takich jak części i usług. Pierwsze pytanie, czy chcesz odroczyć wybór klasy realizacji do konkretnych wdrożeń. Jeśli więc trzeba użyć trochę wdrażania wtyczki. Gdy używasz wtyczek to jest to istotne, że montaż wtyczek odbywa się oddzielnie od reszty aplikacji, dzięki czemu można z łatwością zastąpić różne konfiguracje dla różnych wdrożeń. Jak to osiągnąć, jest drugorzędne. Ten mechanizm konfiguracji można albo skonfigurować usługi lokalizatora lub użyj zastrzyk konfigurowanie obiektów bezpośrednio.
Niektóre inne zagadnienia

W tym artykule, ja koncentruje się na podstawowych kwestiach konfiguracji usług wykorzystujących Dependency Injection i usługi lokalizatora. Jest kilka tematów, które grają w to, które również zasługują na uwagę, ale nie miałem jeszcze czasu, aby odkryć. W szczególności nie jest kwestia cyklu zachowań. Niektóre elementy mają różne wydarzenia w cyklu życia: stop i zaczyna na przykład. Innym problemem jest rosnące zainteresowanie wykorzystaniem proporcji zorientowanych pomysły z tych pojemników. Chociaż nie za ten materiał w artykule w tej chwili, mam nadzieję napisać więcej na ten temat albo poprzez rozszerzenie tego artykułu lub pisząc inny.

Możesz dowiedzieć się dużo więcej o tych pomysłów patrząc na stronach internetowych poświęconych lekkich pojemnikach. Surfing z picocontainer i wiosennych stron internetowych przyczyni się do Ciebie na znacznie więcej omówienie tych zagadnień oraz start na kilka dalszych kwestii.
Myśli końcowe

Obecny pęd lekkich pojemnikach mają wspólny podstawowy wzór do jak robią zespół obsługi – zależność wzór wtryskiwacza. Dependency Injection jest użyteczną alternatywą do Service Locator. Podczas budowania klas aplikacji oba są mniej więcej równoważne, ale myślę, Obsługa Locator ma lekką przewagę ze względu na łatwiejsze zachowanie. Jednak jeśli budynek klasy mają być wykorzystywane w wielu aplikacjach następnie Dependency Injection jest lepszym wyborem.

Jeśli korzystasz z Dependency Injection istnieje wiele stylów do wyboru. Proponuję następujące konstruktora wtrysku chyba napotkasz jednego z określonych problemów z tym podejściem, w którym to przypadku przełączenia do wstrzykiwań setter. Jeśli decyduje się na budowę lub uzyskać pojemnik, wygląda na taki, który obsługuje zarówno konstruktora i wtrysk setter.

Wybór pomiędzy lokalizatora usług i Dependency wtrysku jest mniej ważne niż zasady rozdziału konfigurację usługi z korzystania z usług w aplikacji.