fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
4 minut

Antywzrorzec Service Locator


11.02.2016

Wiecie jaka jest definicja wzorca projektowego, prawda? Za wikipedią: “a general repeatable solution to a commonly occurring problem in software design“. Czym zatem będzie antywzorzec? Czymś takim: “a general repeatable anti-solution to a commonly occurring problem in software design“. Czyli: recepta na napytanie sobie biedy. Czerwony pijany znak z napisem: “Nie idź tą drogą”.

Antywzorzec

Niektórzy będą wam mówić, że Service Locator to wzorzec. Że tak trzeba. I że rozwiązuje problemy. Widziałem takie stwierdzenia nawet na technicznych prezentacjach. Nie słuchajcie fałszywych proroków! A jeśli ktoś tak wam powie, to z wielką ostrożnością podchodźcie do wszelkich innych rekomendacji płynących z ich kłamliwych ust.

Service locator is the root of all dependency evil

W kontekście dependency injection, parafrazując słynne zdanie o optymalizacji: “service locator is the root of all evil“.

Poznajcie się…

Ale co to za ustrojstwo? Service Locator w połączeniu z dowolnym kontenerem Dependency Injection można zobrazować bardzo prosto. Polega to na udostępnieniu kontenera dla całej aplikacji! Każdy kawałek kodu może odwołać się bezpośrednio do niego, aby pobrać zależność, której aktualnie potrzebuje.

To takie “spimpowane new”. Gdybyśmy nie mieli kontenera, to musielibyśmy w różnych miejscach tworzyć nowe obiekty “ręcznie”. I ich zależności: też ręcznie. I zależności tych zależności: również ręcznie. Cały baobab (o którym to baobabie pisałem w poście “DI: kontener“). Ale masakra, prawda? I nagle objawienie: przecież mój obiekt “główny” może przyjąć kontener jako zależność i powyciągać sobie z niego wszystko to co chce!

Service Locator to spimpowane “new”. A operator “new” to też antywzorzec.

Posługując się “constructor injection”, czyli dostarczaniem zależności poprzez parametry konstruktora (najczęściej jedyne słuszne “injection”), deklaracja konstruktora ciągnie się i ciągnie. A to podobno źle, co nie? Ale nie da się inaczej, przecież ta moja klasa NAPRAWDĘ potrzebuje 15 zależności!

EUREKA! W takim razie te 15 zależności zastąpię jedną – kontenerem – i wszystko co mi potrzebne wyciągnę sobie z niego w trakcie pisania metod. Klasa będzie więc miała tylko jedną zależność. Zajebisty ze mnie modelarz! Pięknie, i niech się odczepią!

Prawdziwe oblicze

Jednak to oczywiście nie takie proste. Co nam da przekazanie kontenera w taki sposób? Patrząc realnie: absolutnie NIC. Prawie każda klasa będzie go potrzebowała, więc równie dobrze możemy wystawić go jako statyczne pole w statycznej klasie “DependencyMeneżer” i przestać udawać, że dbamy o wysoką jakość kodu.

clip_image001

Jeżeli będziemy korzystać z kontenera bezpośrednio, wszystkie zależności będą ukryte dokładnie tak samo jakbyśmy w ogóle olali Dependency Injection. Wówczas de facto to robimy: olewamy DI. Gdzie są zależności? W kontenerze. A gdzie jest wstrzykiwanie? Ano… nigdzie. Zamiast Dependency Injection uzyskujemy Dependency Hell.

Service Locator to jak zalepianie gnijącej rany szarą taśmą klejącą.

W takim przypadku tracimy wszystkie zalety płynące z DI. Zarządzanie czasem życia obiektów? Znika. Jawne deklarowanie zależności? Znika. Zasada Single Responsibility Principle, pięknie współgrająca z DI? Znika. Testowalność? Znika.

Tracimy zalety, ale wada pozostaje. Ta wada to sam fakt użycia kontenera, nauka “jak z niego korzystać” i dbanie o jego poprawną konfigurację.

To jak skasowanie testów, bo przeszkadzają w pisaniu kodu. To jak implementowanie security jedynie poprzez ukrywanie guzików na stronie. To jak jedzenie chipsów “zdrowych, prosto z pieca” na diecie. To jak zalepianie gnijącej rany szarą taśmą klejącą.

Don’t. Just don’t.

Po prawdziwie dobre praktyki związane z Dependency Injection odsyłam do niedawnego posta “DI: 3 calls pattern“.

Notify of
bonzo
bonzo

Co z service locator w kontrolerach webapi/mvc? Jeżeli mamy kilka akcji w kontrolerze, a każda korzysta z innej zależności, to przyjęcie ich wszystkich w konstruktorze wydaje się być nieracjonalne, ponieważ wywołanie danej akcji kontrolera spowoduje z-resolve-owanie wszystkich jego zależności, a następnie użycie zapewne tylko jednej z nich. Wydaje mi się więc, że service locator w pewnych miejscach ma zastosowanie.

Szymon Pobiega

Jeśli każda akcja kontrolera korzysta z innych zależności to może oznaczać, że coś jest nie tak ze spójnością tego kontrolera…

Mr. T
Mr. T

Jeśli zdarzy Ci się, że masz jakąś zależność, która nie jest wymagana we wszystkich akcjach kontrolera, to możesz ją wstrzyknąć za pomocą Lazy myLazyDependency. Dopiero odwołanie się do myDependency.Value spowoduje “resolve” tej zależności, a nie bezpośrednio przy wstrzykiwaniu do konstruktora. Podobny efekt można uzyskać wstrzykując przez Func, aczkolwiek jeśli potrzebujesz tylko jednej zależności, Lazy powinno być wystarczające.

tomaszk-poz
tomaszk-poz

gdzies widziałem (Castle Windsor?) wstrzykiwanie przez property w klasie

pawelek

No ale i tak chyba większość to robi :) A swoją drogą: Zarządzanie czasem życia obiektów? Znika. Jawne deklarowanie zależności? Znika. Zasada Single Responsibility Principle, pięknie współgrająca z DI? Znika. Testowalność? Znika. – Potwierdzam w 100% -> do tego sprowadza się jak do “tzw DI” używasz Sesji którą wszędzie wstrzykujesz, a nawet nie wstrzykujesz, tylko wymagasz, na zmianę z kontenerem Context.. To jak skasowanie testów, bo przeszkadzają w pisaniu kodu. – widziałem to, To jak implementowanie security jedynie poprzez ukrywanie guzików na stronie. – to też widziałem, To jak jedzenie chipsów “zdrowych, prosto z pieca” na diecie. – to też… Read more »

Daniel
Daniel

Zbyt radykalny ten post. Co jeżeli w aplikacji WPF chcę stworzyć widok z viewmodelem dopiero gdy użytkownik w niego kliknie? Przeważnie kończy się to tak, że jakaś klasa dostaje dostęp do kontenera i tworzy widoki w trakcie życia aplikacji. Co jeżeli chcę stworzyć proste DTO i wysłać dane na serwer? Mam jak ognia unikać słowa new? Według mnie ten pattern z tworzeniem wszystkich zależności i zwalnianiem kontenera zaraz na starcie aplikacji występuje tylko w książkowych przykładach. Może jedynie w aplikacji webowej komuś uda się to uzyskać, gdzie faktycznie drzewo zależności jest tworzone per request. W aplikacji desktopowej tego nie widzę.… Read more »

Bogusz Pękalski

Święta prawda. Oczywiście są miejsca gdzie się to przydaje, np. resolve’owanie (piękne słowo btw) handlerów dla query/commandów.

PiotrB

“Klasa będzie więc miała tylko jedną zależność. Zajebisty ze mnie modelarz! ”
Tylko, że już tutaj koleś mija się z prawdą, zamazałbym mu model wytykając zależności wewnętrzne. Później karczycho i do poprawy. I proszę mi tu nie pisać o radykałach!

Paweł Maga

A jakie masz zdanie na wykorzystanie Service Locatora przy atrybutach PostSharpowych? Wstrzyknac po bozemu nie da sie, kazde rozwiazanie wydaje sie nie do konca poprawne. Znalazlem 2 rozwiazania: Przyklad bez service locatora: public class MyAttribute : MethodInterceptionAspect { public static Func CreateSomething { get; set; } public override void OnInvoke(MethodInterceptionArgs args) { ISomething something = CreateSomething (); } } I przy budowaniu kontenera: MyAttribute .CreateSomething = () => container.Resolve(); Z service locatorem: public class MyAttribute : MethodInterceptionAspect { private ISomething_something; public ISomething Something => _something ?? (_something = ServiceLocator.Current.GetInstance()); public override void OnInvoke(MethodInterceptionArgs args) { } } Pierwszy przyklad oczywiscie… Read more »

Paweł Maga

Oczywiscie powyzszy kod nie bedzie sie kompilowal, w komentarzach ladnie ucina kazde: ‘ ‘.

Michal Franc

Ta biblioteka powinna byc tylko uzywana jak odziedziczyles burdel z service locatorem :) Nie ma sensu powielac bledow.

Paweł Maga

Na początku byłem zafascynowany tą biblioteką, poźniej zaczęły wychodzić takie kwiatki, ale mimo to znalazło się kilka miejsc gdzie aspekty dużo uprościły kod. Najbardziej bolą mnie problemy z testowaniem takiego kodu, gdy jest taka “zależność”, zarówno przy statycznym kontenerze, jak i statycznym Func są problemy. Ale nie o tym ten artykuł. Chciałem tylko zaznaczyć, ze nie zawsze białe jest białe, a czarne jest czarne. Czasem użycie nawet takiego antipatternu może nam poprawić jakość kodu i jest to jedno z lepszych możliwych rozwiązań. Zbyt restrykcyjne trzymanie się zasad w programowaniu do niczego dobrego nie prowadzi, później wychodzi taki student z uczelnii… Read more »

Marcin
Marcin

Stosując ostatnio intereceptor potrzebowałem jakiejś zależności z kontenera, tyle, że nie wiedziałem dokładnie jakiej, bo dopiero w runtime’ie mogłem to okreslić. Wtedy użyłem czegoś w stylu “interceptionContext.Kernel.Resolve(runtimeType)”.
Czy to własnie nie jest czasem też Service Locator, tyle, że zgrabnie opakowany w kontekst interceptora?

trackback

Antywzrorzec Service Locator

Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl

gibasmaciej
gibasmaciej

Ze smutkiem stwierdzam że zdarzyło mi się popełnić ten błąd :( Nie ma wytłumaczenia dla użycia tego %^}*#£. Poprawia się to tez nieprzyjemnie bo najcześciej rozchodzi się jak zaraza po całym systemie.

Patryk
Patryk

W sumie ten pattern można by potraktować jak wirus.

Marcin
Marcin

Wydaje mi się, że jedynym sensownym i wybaczalnym użyciem Service Locatora jest sytuacja, kiedy np. używamy jakiejś biblioteki powiedzmy do walidacji i zdefiniowane walidatory tworzone są poprzez “new” i nie ma możliwości wstrzyknięcia swoich zależności przez konstruktor. Inne opcji nie bardzo widzę…
Co Wy na to?

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Facebook

Książka

Zobacz również