Wpis z mikrobloga

Od pewnego czasu w swoich projektach do dependency injection używam mega uproszczonej wersji ServiceLocator. Wygląda to mniej więcej tak (c#)

internal abstract class IServices

{

public virtual Collaborator Collaborator { get; protected set; };

...

}


internal class Services : IServices

{

public Services()

{

base.Collaborator = new Collaborator(); //(*)

}

}


internal class XXX /// jakaś fajna skrótowa nazwa projektu

{

public static IServices Services = new Services();

}


//(*) to można załatwić refleksją - która będzie i tak uruchomiona tylko raz per uruchomienie programu


W ten sposób nigdy "nie szyję" zależności, tylko odwołuję się przez XXX.Services.Collaborator.DoSmthg().

Każdy konkretny service jest już normalną klasą, z góry zakładam, że jest bezstanowy (polecam ten styl życia) i ma domyślny konstruktor. Używając nunita jest to testowalne (wymaga troche brzydkiego myku - opieram się na nazwie testu, tj. gdy test odwołuje się do jakiegoś servicu po raz pierwszy, to wtedy tworzę double'a - nową instancję dla każego testu), w kodzie 'produkcyjnym' używa się wybitnie wygodnie - można żonglować zależnościami do woli i bez bólu.

Po co o tym pisze? Bo 1) #chwalesie i 2) rozwiązanie wydaje mi się aż za proste i szukam ukrytych wad ( ͡° ͜ʖ ͡°)

edit: nie umiem zrobić spacji ( ͡° ʖ̯ ͡°)
#programowanie #csharp #ddd #tdd #designpatterns
  • 12
@Yahoo_: koło na nowo - trochę. Wady na wiki:
- moje serwisy są bezstanowe, problem wątkowości rozwiązany,
- nie utrudniają testów, ponieważ każdy test ma swoją instancję zależności
- nie ma opcji, by brakowało zależności (załatwia to trywialny test, którego nie trzeba modyfikować po dodaniu nowych zależności)

Dlaczego nie standardowe podejście? Bo większość rozwiązań, które widziałem, opierała się na słownikach (typ, instancja) lub podobnych rozwiązaniach. Moje bazuje na konkretach i ma
@sasik520: Ukryta wada to… po co Ci ServiceLocator?

Po pierwsze są już różne istniejące implementacje, więc rozumiem, że tworzysz nową dla sportu, a nie na produkcję?

Po drugie (i to ważniejsze): nie uzależniaj swojego kodu od SL. To odwrotnie: SL może być „centrum” aplikacji, gdzie budujesz wszystkie obiekty, konfigurujesz – ale reszta aplikacji ma nie wiedzieć o SL.

Przykład o co mi chodzi…

Masz powiedzmy klasę HelloController. Masz też usługę
@sasik520: Nie znam Twoich wymagań, ale dajmy prosty przykład:
Serwis A używa Serwisu B.
Jak to rozwiązujesz? Strzelam, że masz tam po prostu

class A
{
public void DoSomething()
{
ServiceLocator.B.DoSomethingElse();
//some code here
}
}

Co ma poważną wadę - ukrywasz zależności. Na pierwszy rzut oka nie widać, że A zależy od B (jakby to było w standardowym DI - A prawdopodobnie przyjmowałby B jako parametr konstruktora).
Edit. widzę, że
@Yahoo_: tak, ukrycie zależności to faktycznie wada (nie przypadkiem nie wykreśliłem jej z listy wad z wiki :)), tylko... nie kopnęło mnie to jeszcze i zastanawiam się, jak mnie może kopnąć.

@MacDada się faktycznie rozpisał :) nawet prawie wszystko udało mi się zrozumieć. Nie czuję tylko bólu w postaci ograniczenia do bezparametrowych konstruktorów - to się da prosto załatwić metodą typu Initialize, a sam konstruktor ma być bezparametrowy, żeby łatwiej mocki
Niby piszesz

ja już mam okres początkującego dawno za sobą


@sasik520: A z drugiej strony

a sam konstruktor ma być bezparametrowy, żeby łatwiej mocki tworzyć


@sasik520: :P
To nie jest tak. Skoro robisz w kodzie coś, żeby był łatwo testowalny to prawdopodobnie gdzieś indziej sprataczyłeś (w 99% przypadków). Owszem dobry kod jest testowalny, ale to jest tak naprawdę efekt uboczny. Po prostu ktoś kiedyś zauważył, że jedną z cech dobrego
@Yahoo_: masz trochę racji, a trochę nie masz :)

Masz rację z narzucaniem architektury. w moim przypadku to nie problem (wszystko, czego używam w sl jest internal), ale to rzeczywisty i duży minus tego rozwiązania.

Nie masz racji z "nie masz nawet pojęcia, które mocki będą ci potrzebne" - i nie potrzebuję. Każdy mock zostanie stworzony na potrzeby testu w momencie jego pierwszego użycia. Każdy test dysponuje swoim własnym mockiem. Ten
Nie masz racji z "nie masz nawet pojęcia, które mocki będą ci potrzebne" - i nie potrzebuję. Każdy mock zostanie stworzony na potrzeby testu w momencie jego pierwszego użycia. Każdy test dysponuje swoim własnym mockiem. Ten problem jest rozwiązywalny, chociaż jak pierwszy raz na niego wpadłem, to myślałem, że czar mojego sl prysł ;)


@sasik520: Trochę kupuję, trochę nie. To rozwiązuje problem zależności w pewnym sensie. Zastanawia mnie jak sobie radzisz
Odnośnie drugiej części - pewnie, dlatego napisałem "(jasne, ogólna idea jest bardziej uniwersalna (...))". Ale stwierdzenie, że pisanie kodu tak, żeby testowanie było prostsze, jest oznaką spartolenia wciąż uważam za błędne.
Klasyczne DI "lśni" w twoim scenariuszu, czyli gdy chcesz reużyć klasy w innym miejscu. Moje podejście z kolei jest bardzo wygodne w trakcie projektowania komponentu - mogę dowolnie przenosić zależności z jednego komponentu na drugi i nie kosztuje mnie to nic.