Tłumy konstruujące jedno wdrożenie. DI beznadziejny? Używać lokalizatora usług?

14

Załóżmy, że mamy 1001 klientów, którzy konstruują swoje zależności bezpośrednio, zamiast akceptować zastrzyki. Według naszego szefa refaktoryzacja 1001 nie jest opcją. W rzeczywistości nie mamy nawet dostępu do ich źródła, tylko do plików klas.

Powinniśmy „zmodernizować” system, przez który przechodzi 1001 klientów. Możemy refaktoryzować to, co lubimy. Zależności są częścią tego systemu. I niektóre z tych zależności, które mamy zmienić, aby mieć nową implementację.

Chcielibyśmy mieć możliwość konfigurowania różnych implementacji zależności, aby zaspokoić tę liczbę klientów. Niestety DI nie wydaje się opcją, ponieważ klienci nie akceptują zastrzyków z konstruktorami lub ustawiaczami.

Opcje:

1) Refaktoryzuj implementację usługi, z której korzystają klienci, aby spełniała ona potrzeby klientów. Bang, skończyliśmy. Nie elastyczny. Niezbyt skomplikowane.

2) Refaktoryzuj implementację, aby delegowała pracę do kolejnej zależności, którą nabywa za pośrednictwem fabryki. Teraz możemy kontrolować, z której implementacji korzystają wszyscy, poprzez refaktoryzację fabryki.

3) Przeprowadź refaktoryzację implementacji, aby delegowała pracę do kolejnej zależności, którą uzyskuje za pośrednictwem lokalizatora usług. Teraz możemy kontrolować, z której implementacji korzystają wszyscy, konfigurując lokalizator usług, który może być po prostu hashmapciągiem znaków do obiektów z niewielkim rzutowaniem.

4) Coś, o czym nawet jeszcze nie pomyślałem.

Cel:

Zminimalizuj szkody projektowe spowodowane przeciąganiem starego, źle zaprojektowanego kodu klienta w przyszłość bez zwiększania bezsensownej złożoności.

Klienci nie powinni wiedzieć ani kontrolować wdrażania swoich zależności, ale nalegają na ich budowanie new. Nie możemy kontrolować, newale kontrolujemy klasę, którą budują.

Moje pytanie:

Czego nie wziąłem pod uwagę?


Pytania od doktora Browna

czy naprawdę potrzebujesz możliwości konfiguracji między różnymi implementacjami? W jakim celu?

Zwinność. Wiele niewiadomych. Kierownictwo chce możliwości zmian. Tracą jedynie zależność od świata zewnętrznego. Także testowanie.

czy potrzebujesz mechaniki czasu wykonywania, czy po prostu mechaniki kompilacji czasu, aby przełączać się między różnymi implementacjami? Dlaczego?

Prawdopodobnie wystarcza mechanika czasu kompilacji. Z wyjątkiem testów.

jakiej szczegółowości potrzebujesz, aby przełączać się między implementacjami? Wszystko na raz? Na moduł (każdy zawierający grupę klas)? Na zajęcia?

Ze 1001 tylko jeden jest uruchamiany w systemie w dowolnym momencie. Zmiana, z której korzystają wszyscy klienci naraz, jest prawdopodobnie w porządku. Prawdopodobnie ważna jest jednak indywidualna kontrola zależności.

kto musi kontrolować przełącznik? Tylko Twój zespół programistów? Administrator? Każdy klient na własną rękę? A może deweloperzy konserwacji kodu klienta? Jak łatwa / niezawodna / niezawodna musi być mechanika?

Dev do testowania. Administrator wraz ze zmianami zależności zewnętrznych urządzeń. Musi być łatwy do przetestowania i skonfigurowania.


Naszym celem jest pokazanie, że system można szybko przerobić i zmodernizować.


rzeczywisty przypadek użycia przełącznika implementacji?

Po pierwsze, niektóre dane będą dostarczane przez oprogramowanie, dopóki rozwiązanie sprzętowe nie będzie gotowe.

candied_orange
źródło
1
Nie sądzę, że osiągniesz wiele, przechowując fabryki lub lokalizatory w stanie globalnym. Po co się męczyć? Czy zamierzasz przetestować klientów zastępujących zależność?
Basilevs,
Rozważ użycie niestandardowego modułu ładującego klasy.
Basilevs,
Z ciekawości, co robi ten system?
Prime

Odpowiedzi:

7

Cóż, nie jestem pewien, czy całkowicie rozumiem szczegóły techniczne i ich dokładne różnice w obsługiwanych rozwiązaniach, ale IMHO najpierw musisz dowiedzieć się, jakiego rodzaju elastyczności naprawdę potrzebujesz.

Pytania, które musisz sobie zadać to:

  • czy naprawdę potrzebujesz możliwości konfiguracji między różnymi implementacjami? W jakim celu?

  • czy potrzebujesz mechaniki czasu wykonywania, czy po prostu mechaniki kompilacji czasu, aby przełączać się między różnymi implementacjami? Dlaczego?

  • jakiej szczegółowości potrzebujesz, aby przełączać się między implementacjami? Wszystko na raz? Na moduł (każdy zawierający grupę klas)? Na zajęcia?

  • kto musi kontrolować przełącznik? Tylko Twój zespół programistów? Administrator? Każdy klient na własną rękę? A może deweloperzy konserwacji kodu klienta? Jak łatwa / niezawodna / niezawodna musi być mechanika?

Mając na uwadze odpowiedzi na te pytania, wybierz najprostsze rozwiązanie, jakie możesz wymyślić, które zapewnia wymaganą elastyczność. Nie wprowadzaj żadnej elastyczności, której nie jesteś pewien „na wszelki wypadek”, jeśli oznacza to dodatkowy wysiłek.

W odpowiedzi na twoje odpowiedzi: jeśli masz pod ręką co najmniej jeden rzeczywisty przypadek użycia, użyj go, aby zweryfikować swoje decyzje projektowe. Skorzystaj z tego przypadku użycia, aby dowiedzieć się, które rozwiązanie jest dla Ciebie najlepsze. Po prostu wypróbuj, czy „fabryka” lub „lokalizator usług” zapewnia ci to, czego potrzebujesz, lub potrzebujesz czegoś innego. Jeśli uważasz, że oba rozwiązania są równie dobre dla twojej sprawy, rzuć kostką.

Doktor Brown
źródło
Dobre pytania! Proszę zobaczyć aktualizację.
candied_orange
Zaktualizowany o rzeczywisty przypadek użycia.
candied_orange
@CandiedOrange: Nie mogę dać ci ryb, mogę tylko spróbować pomóc ci łowić samemu :-)
Doc Brown
Zrozumiany. Patrzę tylko na wodę, zastanawiając się, czy potrzebuję lepszej przynęty lub innej dziury. Chciałbym zrobić właściwe DI, ale wydaje się, że ta sytuacja na to nie pozwala.
candied_orange
1
@CandiedOrange Nie rozłącz się z chęcią zrobienia DI, ponieważ jest to „dobre” lub „lepsze” niż cokolwiek innego. DI jest jednym szczególnym rozwiązaniem problemu (lub czasem kilku). Ale nie zawsze jest to najbardziej „odpowiednie” rozwiązanie. Nie zakochaj się w nim ... traktuj to tak, jak jest. To narzędzie.
svidgen
2

Żeby upewnić się, że dobrze to rozumiem. Masz jakąś usługę złożoną z niektórych klas, powiedzmy C1,...,Cni kilku klientów, którzy bezpośrednio dzwonią new Ci(...). Więc myślę, że zgadzam się z ogólną strukturą twojego rozwiązania, która polega na utworzeniu nowej usługi wewnętrznej z nowymi klasami, D1,...,Dnktóre są ładne i nowoczesne i wstrzykują ich zależności (pozwalając na takie zależności przez stos), a następnie przepisuję, Ciaby nie robić nic poza tworzeniem instancji i delgate do Di. Pytanie brzmi, jak to zrobić, a zasugerowałeś kilka sposobów na 2i 3.

Podać podobną sugestię 2. Możesz użyć wstrzykiwania zależności w całym Dii wewnętrznie, a następnie utworzyć normalny katalog główny kompozycji R(używając frameworka, jeśli uważasz to za właściwe), który jest odpowiedzialny za złożenie wykresu obiektowego. Schowaj się Rza statyczną fabryką i pozwól każdemu Ciprzejść Diprzez to. Na przykład możesz mieć

public class OldClassI {
    private final NewClassI newImplementation;

    public OldClassI(Object parameter) {
        this.newImplementation = CompositionRoot.getInstance().getNewClassI(parameter);
    }
}

Zasadniczo jest to twoje rozwiązanie 2, ale gromadzi wszystkie fabryki w jednym miejscu wraz z resztą zastrzyku zależności.

walpen
źródło
Zgadzam się z tym. Nie jestem pewien, czy to nie to samo, co lokalizator usług z opcji 3. Czy spodziewasz się zbudować nową instancję przy każdym wywołaniu do getInstance()?
candied_orange
@CandiedOrange Jest to prawdopodobnie zwykłe zderzenie w użyciu, dla mnie lokalizator usług jest bardzo konkretnie czymś, co jest w zasadzie tylko mapą typów na obiekty, podczas gdy ten katalog główny kompozycji jest po prostu obiektem z wieloma metodami, które konstruują inne obiekty.
walpen
1

Zacznij od prostych, niczym wymyślnych, pojedynczych implementacji.

Jeśli później musisz utworzyć dodatkowe implementacje, jest to pytanie, kiedy nastąpi decyzja o implementacji.

Jeśli potrzebujesz elastyczności w czasie instalacji (każda instalacja klienta korzysta z jednej, statycznej implementacji), nadal nie musisz robić nic wymyślnego. Po prostu oferujesz różne biblioteki DLL lub SO (lub dowolny inny format lib). Klient musi tylko umieścić właściwy w libfolderze ...

Jeśli potrzebujesz elastyczności środowiska wykonawczego, potrzebujesz jedynie cienkiego adaptera i mechanizmu wyboru implementacji. Niezależnie od tego, czy korzystasz z fabryki, lokalizatora czy kontenera IoC, jest to w większości dyskusyjne. Jedyne znaczące różnice między adapterem a lokalizatorem to A) Nazewnictwo i B) Czy zwracany obiekt jest singletonem czy dedykowaną instancją. A duża różnica między kontem IoC a fabryką / lokalizatorem polega na tym, kto do kogo dzwoni . (Często jest to kwestia osobistych preferencji.)

svidgen
źródło