Alternatywa dla kontenera DI

0

Cześć
Ostatnio czytam sobie rzeczy i spotkałem się z opinią, że kontener DI to ZUO. Załóżmy że chcę porzucić kontener, ale nie rezygnować z DI. Tylko jeśli nie kontener to co? Szukałem alternatywy jak używać z DI bez kontenera i niestety bez rezultatów. Gdzieś te instancje muszą zostać stworzone. Gdzie? Nie wyobrażam sobie klejenia wszystkiego ręcznie w Run'ie czy jak tam nazwiemy entry point. Z resztą (moim skromnym zdaniem) dalej jest to kontenerowe podejście - tylko zamiast używać framework'a to wykuwamy koło od nowa

Tak więc, czy macie jakąś sensowną alternatywę dla kontenera DI?

1

Nie wyobrażam sobie klejenia wszystkiego ręcznie w Run'ie czy jak tam nazwiemy entry point

Tak właśnie to wygląda bez kontenera ;-)
Przy czym jeśli wykorzystasz wzorzec fabryka, staje się to całkiem przyjemne w kodowaniu.

0

Nie wyobrażam sobie klejenia wszystkiego ręcznie w Run'ie czy jak tam nazwiemy entry point.

Nie musisz tworzyć wszystkiego w jednej metodzie. Możesz ją podzielić na klasy i metody. Jeśli jawna konstrukcja grafu zależności będzie mocno skomplikowana to najprawdopodobniej będzie to oznaczało iż aplikacja jest słabo zmodularyzowana (tzn jest zbyt dużo powiązań między odległymi klasami). Jakub Nabrdalik pokazuje na swoich prezentacjach modularyzację opartą o package scope w Javie. W takiej sytuacji wydaje mi się, że wstrzykiwanie zależności mogłoby odbywać się dwupoziomowo - każdy pakiet ma własną logikę do konstruowania hierarchii zależności z tego pakietu, a ponad tym jest jeszcze logika spinająca moduły (pakiety) do kupy.

0

Możesz podzielić projekt na moduły, gdzie każdy moduł tworzy się sam (na zasadzie, że tworzysz po prostu obiekt modułu, czy to jakimś obiektem z konfiguracji, czy czymś). Tak, nadal potrzebujesz jednego miejsca, żeby to spiąć do kupy przez co musisz zrobić to sam (rozwiązując od razu problemy z zależnościami. Przy okazji na pewno unikniesz cykli w zależnościach modułów). W zamian za to dostajesz pełną kontrole nad tym co, kiedy, gdzie i jak startuje.

0

@Tyvrel: To popatrz sobie jak działa Guice. I zasadniczo ręczne wstrzykiwanie zależności jest (w dużym uproszczeniu) prawdopodobnie bardzo podobe...

0

Jakoś się dało pisać te programy bez użycia kontenerów DI. Co do tego zła, to też można wybrać większe i mniejsze. Może popadam w paskudny relatywizm moralny, ale jednak czym innym jest taki Spring i jego "znajdź wszystkie beany implementujące jakiś tam interface i wstrzyknij jako listę bezpośrednio do pola prywatnego obiektu", a czym innym taki Dagger 2 z jego "napisz mi metody fabrykujące do wskazanych klas".

1

Odniose się do tego komentarza, bo mnie ciśnienie podnosi :-)

` Jakoś się dało pisać te programy bez użycia kontenerów1 DI dało sie też pisać w asemblerze, a potem bez bibliotek, a potem bez gotowych komponentów jak bazy danych czy kolejki... ;) Nadal się da! - Shalom 2018-12-06 09:27

To jest przykład tak zwanej fałszywej alternatywy. Można używać bibliotek. Można uzywać bardziej zwięzłych języków programowania, a nadal nie widzieć korzyści w kontenerowym DI.
Ja nie widze. To znaczy widzę trochę strat, które nie są rekompensowane przez potencjalne korzyści. Mam mniej więcej o kontenerowym DI takie zdanie jak o GOTO.
Jak weźmiesz bardzo mały lub specyficzny fragment kodu to pokażesz, że dzieki GOTO (Adnotacjom, DI) można coś zrobić szybciej. W średniej wielkości kodzie (więcej niż jeden CRUD :-) ) już różnica ilości kodu wychodzi praktycznie żadna, widzę za to bajzel jaki ta magia powoduje.

Mam nawet taką metrykę spustoszenia springowego/DI - klasa z największą ilością @Autowired/ Injectionów. 15tki-18tki to normalka.

Żeby nie było, kiedyś (na pewno jeszcze w 2013) kontenery, adnotacje mnie bardzo jarały i byłem praktykującym wyznawcą.

Najlepszy zresztą przykład, ze kontenery DI nie są potrzebne daje ... Spring. Nie znam kodu wszystkich modułów, ale te co trochę znam nie używają Springa(!), ani żadnego kontenerowego DI , mimo że mogłyby, bo od spring-core/spring-beans zależą :-). Przykład Spring-data-jpa, spring-tx. Może kod nie jest idealny, ale całkiem ładnie pokazuje jak fabryki rozwiazują problem.

2
jarekr000000 napisał(a):

Mam nawet taką metrykę spustoszenia springowego/DI - klasa z największą ilością @Autowired/ Injectionów. 15tki-18tki to normalka.

To mnie zawsze zastanawia. Czy jeżeli by zabrać możliwość ałtołajerowania, to czy ludzie by otrzeźwieli i pisaliby klasy bez przesadzania z zależnościami? Mam wrażenie, że konstruktor to taki bicz na lenistwo i gdyby wymusić korzystanie z niego dla zależności, to już łatwiej byłoby zorganizować odpowiednią strukturę klas. Z drugiej strony kontenery wcale nie przeszkadzają w rozsądnym pisaniu kodu (bardziej ułatwiają bylejakość) i kocham Daggera 2.

2

Wymuszenie ceremonii przepisywania argumentów z konstruktora do pól prywatnych pewnie mogłoby minimalnie poprawić sytuację, ale dalej magiczne wstrzykiwanie oznaczałoby, że nikt nie wiedziałby jak naprawdę wygląda graf zależności. Chociaż z drugiej strony jakieś narzędzia do wizualizacji grafu są, np: https://github.com/google/guice/wiki/Grapher Przy wielu klasach z kilkunastoma wstrzykniętymi parametrami taki graf zależności pewnie byłby nie do ogarnięcia przez zwykłego śmiertelnika.

1
jarekr000000 napisał(a):

To jest przykład tak zwanej fałszywej alternatywy. Można używać bibliotek. Można uzywać bardziej zwięzłych języków programowania, a nadal nie widzieć korzyści w kontenerowym DI.
Ja nie widze. To znaczy widzę trochę strat, które nie są rekompensowane przez potensjalne korzyści. Mam mniej więcej o kontenerowym DI takie zdanie jak o GOTO.
Jak weźmiesz bardzo mały lub specyficzny fragment kodu to pokażesz, że dzieki GOTO (Adnotacjom, DI) można coś zrobić szybciej. W średniej wielkości kodzie (więcej niż jeden CRUD :-) ) już różnica ilości kodu wychodzi praktycznie żadna, widzę za to bajzel jaki magia powoduje.

Mam nawet taką metrykę spustoszenia springowego/DI - klasa z największą ilością @Autowired/ Injectionów. 15tki-18tki to normalka.

Nikt nie każe wstawiać zależności przez @Autowired na polach. Jest kolosalna różnica pomiędzy dynamic proxy ze Springa i wstrzykiwaniem zależności refleksjami do klasy, a automatycznym tworzeniem fabryk przez takiego Daggera 2. Warto pamiętać, że te biblioteki / frameworki nikogo nie zmuszają to pisania kiepskiego kodu. Jak ktoś walnął klasę z 20 zależnościami to problemem jest głupota programisty a nie DI. Chociaż oczywiście Spring potrafi czasami zamaskować partactwo.

1

Z tym dagerrem to mam problem, bo wszyscy pisza jaki jest lepszy od innych magii. Compile time etc. A ja widze tylko kolejną kaszankę :
Przykład http://www.vogella.com/tutorials/Dagger/article.html

component.inject(this); serio ?

Ale strzelam, że pewnie gdzieś, ktoś ma lepszy przykład.

20 zależnościami to problemem jest głupota programisty a nie DI.

Zawsze jest głupota programisty, ale ja to widze tak, że właśnie jak robimy głupotę to zaczyna być widać zyski z kontenera DI. (to z prezentacji Tomera Gabela).

Jest jeden przypadek, gdzie widzę sens kontenerów DI (opartych o skanowanie klas) - runtimowe rozpoznawanie zależności, o których nic nie wiadomo w czasie kompilacji - czyli pluginy.

0

Zalety DI nie są widoczne jak patrzymy na ten mechanizm uproszczony do 2 klas. Zalety pojawiają się, jak już rozbijemy sobie tę słynną klasę z 20 zależnościami na graf 10 klas zależnych od siebie i zamiast pisać metodę składającą to wszystko do kupy, albo uciekać się do wcale nie aż tak czytelnych i nie aż tak fajnych wzorców. Dla mnie napisanie 10 krótkich metod i wygenerowanie metod łączących to wszystko do kupy jest dużo fajniejsze niż pisanie sporego kawałka nic nie robiącego kodu i zastanawianiu się w którą stronę idą zależności i od czego zacząć składanie.

2

W ręcznym DI właśnie jedną z większych zalet jest to, że jak porobisz potworki z 20-ma zaleznościami to od razu cię to zaboli, więc takie ręczne DI mocno promuje loose coupling. Przy porządnej architekturze graf zależności jest mocno drzewiasty, a więc można sensownie rozbić jego ręczne budowanie na wiele metod czy klas. Porządna architektura = mała ilość powiązań między klasami = mała ilość kodu do ręcznego wstrzykiwania. Kiepska architektura = dużo powiązań między klasami = trudność w podzieleniu kodu do ręcznego wstrzykiwania na małe kawałki.

1

Porządna architektura to nie jest mała liczba powiązań. Jak rozbijesz dużą funkcjonalność na klasy o pojedynczych odpowiedzialnościach, to tych klas będzie dużo i praktycznie wszystkie będą od siebie zależały więc powiązań będzie dużo. Jeżeli klasy będą miały sensownie zaplanowane odpowiedzialności, to jednostkowo każda z tych klas będzie miała bardzo ograniczone zależności (nie wiem czy to właśnie miałeś na myśli). Istotne jest, że jeśli chcesz sobie złożyć aplikację z tych, powiedzmy 50 klocków, to musisz mieć jakieś metody fabrykujące, które w odpowiedniej kolejności utworzą wszystkie potrzebne obiekty i złożą je razem. Taka metoda nie wnosi sobą do działania aplikacji nic istotnego, poza tym, że trzeba ją napisać, stracić na nią czas i usunąć własne błędy. Dlatego wolę takie zadanie pozostawić jakiemuś narzędziu automatyzującemu DI.
Jak masz w projekcie fleję, to 20 argumentowy konstruktor go nie powstrzyma, nawet jak będzie to musiał pisać 10 razy ("ja tego nie piszę, ja to kopiuję"), a jak dostanie po łapach to następnym razem zrobi sobie pusty konstruktor i wstrzykiwanie zależności przez setery, co moim zdaniem jest równie kancerogenne jak Spring ze swoim @Autowired, jeśli nie bardziej, bo wstrzykiwanie na polach przynajmniej da się poprawić.

2

@piotrpo: nie chodzi o małą liczbę powiązań ogólnie, tylko o mała liczbę powiązań między poszczególnymi składnikami

0

Taka metoda nie wnosi sobą do działania aplikacji nic istotnego, poza tym, że trzeba ją napisać, stracić na nią czas i usunąć własne błędy.

Nie wiem skąd Ci się biorą te błędy. W DI albo jest tak, że mamy jedną implementację, wtedy błedu się nie da popełnić bo się nie skompiluje.
Albo mamy więcej implementacji, a wtedy albo popełnimy bład w kodzie, albo w XMLach czy Adnotacjach. Prawdopodobieństwo podobne, choć IMO w konfiguracjach walnąć się łatwiej.
Jeśli dodatkowo DI korzysta z refleksji / skanowania klas to błąd może wynikać nawet z tego, że ktoś dorzuci jakiegoś jara na produkcję, którego nie było na testach, a to już jest zupełnie słabe.

Edit:

następnym razem zrobi sobie pusty konstruktor i wstrzykiwanie zależności przez setery,

To raczej niemożliwe, przecież musiałby ktoś zrobić pole niefinalne. A tego przecież czynić nie wolno.

2

Zrobiłem jakąś bzdurkę składaną na oba spoby https://github.com/piotrpo/dagger Nie używałem Daggera już jakiś czas i podejście tradycyjne faktycznie okazało się szybsze w pisaniu. Dagger jednak wymaga jakiegoś tam wkładu na start, plus taki, że jest to już gotowe rozwiązanie - bierzesz, nie musisz się zastanawiać w jakim pakiecie umieścić te wszystkie klasy konstruujące, czy zastosować fabrykę abstrakcji, czy może jednak proxy będzie bardziej cool itd. Oczywiście popełniłem trochę błędów podczas pisania tych klas, zapominając o jakiejś tam metodzie fabrykującej, adnotacji, czy wskazaniu której implementacji trzeba użyć dla zaspokojenia jakiejś tam zależności - plus taki, że wszystkie te błędy zostały wychwycone przez annotation processor podczas kompilacji i dostałem dość czytelne komunikaty co jest nie tak.

To czego udało się uniknąć dzięki DI to pisania tego kodu (w sumie po to jest ta biblioteka....): https://github.com/piotrpo/dagger/blob/master/src/main/kotlin/pl/com/digita/example/traditional/ApplicationFactory.kt
Niby nie dużo, ale jednak. Co oczywiste nie było konieczności tworzenia konstruktorów o 30 parametrach, odpuściłem sobie też wstrzyknięcia w pola, czy przez setery - jak widać, da się.
W idealizowaniu podejścia "ręcznego" pomija się to, że ludziom często nie chce się stworzyć jakiejś metody, czy ekstra klasy i kończy się właśnie wstrzykiwaniem przez setery, czy chamskim this.dependency = new Dependency() w konstruktorze.

To raczej niemożliwe, przecież musiałby ktoś zrobić pole niefinalne. A tego przecież czynić nie wolno.

Mam kolegów z SE Azji w projekcie i patrząc w kod czuje się czasami klimat targowiska w Sajgonie - nie takie rzeczy tam się pokazują.

2

Zrobiłem pulla gdyby ktoś był ciekawy. Nie edytowałem kodu z klasycznym DI, bo chodziło mi tylko o pokazanie zalet Daggera. Łatwe współdzielenie instancji różnych klas w miarę potrzeby za pomocą zasięgu jak @Singleton albo przy pomocy @BindsInstance, prosta konfiguracja, chowanie implementacji. Chciałem jeszcze pokazać kilka innych zalet, które wynikają moim zdaniem z Daggera, ale potrzebuję bardziej rzeczywistego przykładu. W sumie wpadło mi coś do głowy jak to pisałem, więc może niedługo wrzucę.

2
jarekr000000 napisał(a):

Mam nawet taką metrykę spustoszenia springowego/DI - klasa z największą ilością @Autowired/ Injectionów. 15tki-18tki to normalka.

To patrz tu. Fragment produkcyjnego kodu z mojego projektu. Pewnie jakbym się uparł to bym znalazł lepszą klasę

protected PliSmtPosition pliSmtPosition;
protected PliPositionCancellation pliPositionCancellation;
protected PliPositionClose pliPositionClose;
protected PliPosTimer_ldl pliPosTimer_smt;
protected PliSmtEmployeeCustomer pliSmtEmployeeCustomer;
protected PliSmtPromotionCoupon pliSmtPromotionCoupon;
protected PliSmtPosAdapterRequest pliSmtPosAdapterRequest;
protected SmtPosFunctionManager smtPosFunctionManager;
protected PliRounding pliRounding;
protected PliBelegPersistence pliBelegPersistence;
protected PliSmtTerminal pliSmtTerminal;
protected PliSmtTerminalErrorReaction pliSmtTerminalErrorReaction;
protected PliCompleteRescan pliCompleteRescan;
protected PosFunctionConfigProvider posFunctionConfigProvider;
protected PliPayment_ldl pliPayment;
protected PliSmtPaymentTerminal_ldl pliSmtPaymentTerminal_ldl;
protected PliBelegSigningUtils pliBelegSigningUtils;
protected SignatureConfigProvider signatureConfigProvider;
protected PliPaymentStateSession pliPaymentStateSession;
protected PliValPracUtils pliValPracUtils;
protected PliSmtPaymentValPrac pliSmtPaymentValPrac;
protected PliCurrency pliCurrency;
protected PliSubtotal_ldl pliSubtotal_ldl;
protected PliSmtPrepaid pliSmtPrepaid;
protected PliSmtTransaction pliSmtTransaction;
protected PliMobilePaymentActivation pliMobilePaymentActivation;
protected PliSmtPaymentMobile pliSmtPaymentMobile;
protected PliSmtValuephoneRestoration pliSmtValuephoneRestoration;
protected PliSmtValuephoneProcessStart pliSmtValuephoneProcessStart;
protected PliSmtLolPlusCustomer pliSmtLolPlusCustomer;
protected PliSmtPaymentWirecard pliSmtPaymentWirecard;
protected PliSmtPaymentSocialVoucher pliSmtPaymentSocialVoucher;
protected PliSmtRoundingChangesSession pliSmtRoundingChangesSession;
protected PliDpBonRoundingOperations_ldl pliDpBonRoundingOperations_ldl;
protected PliPaymentMeansRoundingOperations pliPaymentMeansRoundingOperations;
@Inject
public final void setPliSmtPromotionCoupon(PliSmtPromotionCoupon pliSmtPromotionCoupon) {
  this.pliSmtPromotionCoupon = pliSmtPromotionCoupon;
}
@Inject
public final void setPliPosTimer_smt(PliPosTimer_ldl pliPosTimer_smt) {
  this.pliPosTimer_smt = pliPosTimer_smt;
}
@Inject
public final void setPliSmtPosition(PliSmtPosition pliSmtPosition) {
  this.pliSmtPosition = pliSmtPosition;
}
@Inject
public final void setPliPositionCancellation(PliPositionCancellation pliPositionCancellation) {
  this.pliPositionCancellation = pliPositionCancellation;
}
@Inject
public final void setPliPositionClose(PliPositionClose pliPositionClose) {
  this.pliPositionClose = pliPositionClose;
}
@Inject
public final void setPliSmtEmployeeCustomer(PliSmtEmployeeCustomer pliSmtEmployeeCustomer) {
  this.pliSmtEmployeeCustomer = pliSmtEmployeeCustomer;
}
@Inject
public final void setPliSmtPosAdapterRequest(PliSmtPosAdapterRequest pliSmtPosAdapterRequest) {
  this.pliSmtPosAdapterRequest = pliSmtPosAdapterRequest;
}
@Inject
public final void setSmtPosFunctionManager(SmtPosFunctionManager smtPosFunctionManager) {
  this.smtPosFunctionManager = smtPosFunctionManager;
}
@Inject
public final void setPliRounding(PliRounding pliRounding) {
  this.pliRounding = pliRounding;
}
@Inject
public final void setPliBelegPersistence(PliBelegPersistence pliBelegPersistence) {
  this.pliBelegPersistence = pliBelegPersistence;
}
@Inject
public final void setPliSmtTerminal(PliSmtTerminal pliSmtTerminal) {
  this.pliSmtTerminal = pliSmtTerminal;
}
@Inject
public final void setPliSmtTerminalErrorReaction(PliSmtTerminalErrorReaction pliSmtTerminalErrorReaction) {
  this.pliSmtTerminalErrorReaction = pliSmtTerminalErrorReaction;
}
@Inject
public final void setPliCompleteRescan(PliCompleteRescan pliCompleteRescan) {
  this.pliCompleteRescan = pliCompleteRescan;
}
@Inject
public final void setPosFunctionConfigProvider(PosFunctionConfigProvider posFunctionConfigProvider) {
  this.posFunctionConfigProvider = posFunctionConfigProvider;
}
@Inject
public final void setPliPayment(PliPayment_ldl pliPayment) {
  this.pliPayment = pliPayment;
}
@Inject
public final void setPliSmtPaymentTerminal_ldl(PliSmtPaymentTerminal_ldl pliSmtPaymentTerminal_ldl) {
  this.pliSmtPaymentTerminal_ldl = pliSmtPaymentTerminal_ldl;
}
@Inject
public final void setPliBelegSigningUtils(PliBelegSigningUtils pliBelegSigningUtils) {
  this.pliBelegSigningUtils = pliBelegSigningUtils;
}
@Inject
public final void setSignatureConfigProvider(SignatureConfigProvider signatureConfigProvider) {
  this.signatureConfigProvider = signatureConfigProvider;
}
@Inject
public final void setPliPaymentStateSession(PliPaymentStateSession pliPaymentStateSession) {
  this.pliPaymentStateSession = pliPaymentStateSession;
}
@Inject
public final void setPliValPracUtils(PliValPracUtils pliValPracUtils) {
  this.pliValPracUtils = pliValPracUtils;
}
@Inject
public final void setPliSmtPaymentValPrac(PliSmtPaymentValPrac pliSmtPaymentValPrac) {
  this.pliSmtPaymentValPrac = pliSmtPaymentValPrac;
}
@Inject
public final void setPliCurrency(PliCurrency pliCurrency) {
  this.pliCurrency = pliCurrency;
}
@Inject
public final void setPliSubtotal_ldl(PliSubtotal_ldl pliSubtotal_ldl) {
  this.pliSubtotal_ldl = pliSubtotal_ldl;
}
@Inject
public final void setPliSmtPrepaid(PliSmtPrepaid pliSmtPrepaid) {
  this.pliSmtPrepaid = pliSmtPrepaid;
}
@Inject
public final void setPliSmtTransaction(PliSmtTransaction pliSmtTransaction) {
  this.pliSmtTransaction = pliSmtTransaction;
}
@Inject
public final void setPliMobilePaymentActivation(PliMobilePaymentActivation pliMobilePaymentActivation) {
  this.pliMobilePaymentActivation = pliMobilePaymentActivation;
}
@Inject
public final void setPliSmtPaymentMobile(PliSmtPaymentMobile pliSmtPaymentMobile) {
  this.pliSmtPaymentMobile = pliSmtPaymentMobile;
}
@Inject
public final void setPliSmtValuephoneRestoration(PliSmtValuephoneRestoration pliSmtValuephoneRestoration) {
  this.pliSmtValuephoneRestoration = pliSmtValuephoneRestoration;
}
@Inject
public final void setPliSmtValuephoneProcessStart(PliSmtValuephoneProcessStart pliSmtValuephoneProcessStart) {
  this.pliSmtValuephoneProcessStart = pliSmtValuephoneProcessStart;
}
@Inject
public final void setPliSmtLolPlusCustomer(PliSmtLolPlusCustomer pliSmtLolPlusCustomer) {
  this.pliSmtLolPlusCustomer = pliSmtLolPlusCustomer;
}
@Inject
public final void setPliSmtPaymentWirecard(PliSmtPaymentWirecard pliSmtPaymentWirecard) {
  this.pliSmtPaymentWirecard = pliSmtPaymentWirecard;
}
@Inject
public final void setPliSmtPaymentSocialVoucher(PliSmtPaymentSocialVoucher pliSmtPaymentSocialVoucher) {
  this.pliSmtPaymentSocialVoucher = pliSmtPaymentSocialVoucher;
}
@Inject
public final void setPliSmtRoundingChangesSession(PliSmtRoundingChangesSession pliSmtRoundingChangesSession) {
  this.pliSmtRoundingChangesSession = pliSmtRoundingChangesSession;
}
@Inject
public final void setPliDpBonRoundingOperations_ldl(PliDpBonRoundingOperations_ldl pliDpBonRoundingOperations_ldl) {
  this.pliDpBonRoundingOperations_ldl = pliDpBonRoundingOperations_ldl;
}
@Inject
public final void setPliPaymentMeansRoundingOperations(PliPaymentMeansRoundingOperations pliPaymentMeansRoundingOperations) {
  this.pliPaymentMeansRoundingOperations = pliPaymentMeansRoundingOperations;
}
2
jarekr000000 napisał(a):

Jak weźmiesz bardzo mały lub specyficzny fragment kodu to pokażesz, że dzieki GOTO (Adnotacjom, DI) można coś zrobić szybciej. W średniej wielkości kodzie (więcej niż jeden CRUD :-) ) już różnica ilości kodu wychodzi praktycznie żadna, widzę za to bajzel jaki ta magia powoduje.

Jak to dobrze, że są technologie, w których używanie DI nie wymaga wprowadzania bajzlu. :)
Ale intrygujące jest to zdanie o ilości kodu - metody fabrykujące trzeba dopisać do każdej klasy albo w ogóle pisać całe fabryki, a konfiguracja kontenera to jakieś kilkanaście linijek na cały projekt właściwie niezależnie od jego wielkości. Więc jak dla mnie, to tylko w hello world można osiągnąć krótszy kod przez niestosowanie kontenera.

Mam nawet taką metrykę spustoszenia springowego/DI - klasa z największą ilością @Autowired/ Injectionów. 15tki-18tki to normalka.

Pytanie, czy z faktu, iż Java ma tylko jeden słaby kontener DI pozbawiony alternatyw wynika, że cała koncepcja jest zła. Jeśli tak, to łatwo można udowodnić, że każdy Polak to rudy pijak i złodziej. :)

Serio nie można normalnie konfigurować DI, trzeba brudzić w kodzie biznesowym jakimiś podejrzanymi infrastrukturalnymi śmieciami?

1

@jarekr000000: weź Ty w końcu napisz może jakąś mini-książkę o tym DI (piszę na poważnie).
Bo niby to jest sensowne to co piszesz, a nikt kogo znam w środowisku Javy tak nie robi.
Pewnie wyjdzie coś w stylu Yegora Bugayenko, ale chętnie bym przeczytał.
Zresztą, w C++ przecież takiej magii nie ma i jakoś ludzie sobie dają radę.

Zagadnienia:

  • przekazywanie globalnych wartości: aktualny użytkownik, połączenie z bazą
  • łamanie cykli
  • kontrola kolejności startu

W zamierzchłych czasach robiłem coś takiego w Delphi - ale tam masz wprost definiowalną inicjalizację modułów i z tego co pamiętam nawet możesz wymusić kolejność startu.

0
vpiotr napisał(a):

@jarekr000000: weź Ty w końcu napisz może jakąś mini-książkę o tym DI (piszę na poważnie).
Bo niby to jest sensowne to co piszesz, a nikt kogo znam w środowisku Javy tak nie robi.

Już podawałem kto tak robi (czyli nie używa kontenera DI) :

  • Spring :-) (dużo modułów, nie wiem czy wszystkie),
  • duża część softu z którym pracuje (np. taka Camunda/Activiti),
  • robiliśmy tak kiedyś w C++, Javie (zanim ta zaraza konternerowa nadeszła),
  • robię tak po tym jak już ochłonąłem (po zarazie).

Książki nie będzie. To tak jakbyś chciał, żeby ateista wyraził swoje stanowisko w dyksusji o Filioque.
Nie ma o czym pisać. Dla mnie to jakaś bzdura, chwilowa moda, wynaturzenie. Przeminie. IMO już fala zaczyna opadać.

Poza tym:
I was
Pamiętam jak to się rodziło. Najpierw były serwery aplikacji i servlety. Potem się okazało, że zrobiono je tak, że zupełnie beznadzieje się testuje i trzeba było jakoś łatać.
Kontenery DI to była ta łata. W tym najśmieszniejszy to Spring, bo oprócz DI zajmuje się jeszcze aspektami, w sposób w zasadzie skopiowany z JavaEE/EJB. Ewidentnie autorzy cierpieli na inner platform efect (Zrobili swoje JavaEE, wewnątrz JavaEE).

W międzyczasie serwery aplikacji z przestały być potrzebne.... ale cargo cult trwa.

3

Ludzie cierpią na dziwną chorobę, objawia się wstrzykiwaniem wszystkiego co się da, żeby zmniejszyć "coupling".

Pierwszy z objawów to robienie do każdej klasy interfejs.

Drugi to wstrzykiwanie wewnętrznych klas modułu.

Jeśli obiekt nie posiada stanu to przekazywanie go przez konstruktor najczęściej w ogóle nie ma sensu.

0

Czemu akurat to czy obiekt ma stan jest wyznacznikiem czy go przekazywać przez konstruktor?

1
._. napisał(a):

Jeśli obiekt nie posiada stanu to przekazywanie go przez konstruktor najczęściej w ogóle nie ma sensu.

I to by nawet była prawda gdyby nie to, że czasem ktoś coś potrzebuje testować.

0
jarekr000000 napisał(a):
._. napisał(a):

Jeśli obiekt nie posiada stanu to przekazywanie go przez konstruktor najczęściej w ogóle nie ma sensu.

I to by nawet była prawda gdyby nie to, że czasem ktoś coś potrzebuje testować.

Czy tylko mnie razi nazywanie obiektem czegoś bezstanowego?
A nie można użyć "strategii" czy "funktora"?

0
._. napisał(a):

Jeśli obiekt nie posiada stanu to przekazywanie go przez konstruktor najczęściej w ogóle nie ma sensu.

Załóżmy, że mam klasę jakiegoś repozytorium:

public class Repository{
  public Repository(String connectionString){
     (...)
  }
}

W jaki lepszy sposób mam przekazać niemodyfikowalny i bezstanowy obiekt jakim jest connectionString?

2

No nie powiedziałbym, że connectionString jest bestanowy. On ma raczej bardzo jasno określony stan. Bezstanowy obiekt to byłoby coś w stylu:

class StringJoiner {
  String joinStrings(String str1, String str2) {
    return str1 + str2;
  }
}

No i imho właśnie bezstanowie obiekty najczęściej przekazuje się jako zależność przez konstruktor.

immutable != stateless. Niemutowalny obiekt ma stan, po prostu może mieć tylko jeden i nie możesz go zmienić.

2

Bezstanowy obiekt to według mnie taki którego dowolne dwie instancje będą takie same (zachowywać się tak samo).

1
  1. Niemutowalny stan to też stan.

  2. Jeśli testujemy to można mieć potrzebę podmiany bezstanowego elementu. Np w kodzie produkcyjnym mamy:

class RocketLauncher {
  void launchRocket(RocketId rocketId) {
    gov.nasa.Rocket.findById(rocketId).launch();
  }
}

W kodzie testowym chcielibyśmy tę metodę zastąpić czymś co nawet nie próbuje odpalać rakiety.

  1. Jeśli ktoś nie lubi bezstanowych obiektów, ale jednocześnie dla niego lambda, która nie domyka się na żadnym stanie nie jest bezstanowym obiektem (chociaż faktycznie jest) to powyższe można zmienić na lambdę, tzn:
@FunctionalInterface
interface RockerLauncher {
  void launchRocker(RocketId rocketId);
}
// implementacja produkcyjna
RocketLauncher = (rocketId) -> {
  gov.nasa.Rocket.findById(rocketId).launch();
};
// implementacja testowa - dowolna
  1. Z drugiej strony chyba rzeczywiście rzadko takie bezstanowe obiekty wstrzykuję. Nie chce mi się jednak sprawdzać :]

1 użytkowników online, w tym zalogowanych: 0, gości: 1