Alternatywa dla kontenera DI

0
piotrpo napisał(a):
._. 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?

ConnectionString powinien być statyczny, ponieważ są to ustawienia aplikacji, które ustawiasz tylko raz przy starcie.
Poza tym string to raczej typ prymitywny a nie obiekt.

Bezstanowść oznacza że dowolny klient może używać dowolnej instancji tego obiektu bez względu na historie konkretnej instanji.

0

Jeśli muszę odczytać tę wartość z properties, czy innego chmurowego keyvaulta, to statycznie będzie ciężko go zainicjować, a już na 100 nie będzie to typowy constans.

1

Poza tym string to raczej typ prymitywny a nie obiekt.

String to obiekt. W Javie, C++, etc możesz sobie nawet przejrzeć jego kod. Wygląda jak kod każdej innej klasy.

Bezstanowść oznacza że dowolny klient może używać dowolnej instancji tego obiektu bez względu na historie konkretnej instanji.

W sensie, że dowolna instancja ma zwracać takie same wyniki? W takim razie albo ten string w środku będzie nieużywany, albo będzie miał wpływ na wyniki.

ConnectionString powinien być statyczny, ponieważ są to ustawienia aplikacji, które ustawiasz tylko raz przy starcie.

Idąc tym tokiem myślenia klas które instancjonuje się raz też nie powinno się wstrzykiwać. Wystarczy wrzucić referencję do jakiejś zmiennej globalnej. Powodzenia w testowaniu. W sumie nie ma wielkiej różnicy czy masz statyczne serwisy czy statyczne connection stringi, bo connection string semantycznie jest jak referencja do instancji bazy danych (także testowej).

0

Idąc tym tokiem myślenia klas które instancjonuje się raz też nie powinno się wstrzykiwać. Wystarczy wrzucić referencję do jakiejś zmiennej globalnej. Powodzenia w testowaniu. W sumie nie ma wielkiej różnicy czy masz statyczne serwisy czy statyczne connection stringi, bo connection string semantycznie jest jak referencja do instancji bazy danych (także testowej).

A lie razy będziesz zmieniał ten conectionString w teście? :---| 3 razy na test albo więcej ? :-----|

String to obiekt. W Javie, C++, etc możesz sobie nawet przejrzeć jego kod. Wygląda jak kod każdej innej klasy.

Ale ma charakterystykę podobną do primitive i to nie oznacza, że w każdym języku jest on obiektem.

1
._. napisał(a):

A lie razy będziesz zmieniał ten conectionString w teście? :---| 3 razy na test albo więcej ? :-----|

Ja bardzo często odpalam testy asynchronicznie (Erlang, więc można), ale jakiś palant wpadł na genialny pomysł, by konfigurować jakiś moduł przy pomocy globalnego stanu i wuj wszystko strzela, bo bez sensu muszę odpalać wszystko w jednym procesie. Tak, jeśli można to wstrzykujemy string konfiguracyjny, prędzej czy później się przyda. A jak naprawdę chcesz dla własnej wygody jednak mieć konfigurację globalną, to zrób to tak:

public class Repository{
  public Repository() {
    return Repository(global.Stale.get("connectionString");
  }

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

I masz załatwione obie rzeczy na raz - i wygodnie, i testowalnie.

1

A lie razy będziesz zmieniał ten conectionString w teście? :---| 3 razy na test albo więcej ? :-----|

Testy mają być od siebie całkowicie odizolowane. Zabawę w przestawianie globalnego mutowalnego stanu oglądam w projekcie w firmie - poprzedni zespół wybrał framework webowy, który utrudniał wstrzykiwanie zależności i zamiast trzymać się jak najmniejszej ilości zła koniecznego, czyli globalnego mutowalnego stanu, programiści jeden za drugim potraktowali ten globalny mutowalny stan jako wzorzec projektowy. Efektem takiego podejścia są testy, które padają w losowych momentach i nie wiadomo od czego.

Ale ma charakterystykę podobną do primitive i to nie oznacza, że w każdym języku jest on obiektem.

Bez zbędnych dywagacji: aktualnie w Javie (czyli wersje do Javy 12, bo nie wiadomo co będzie później) bardzo łatwo sprawdzić czy coś jest obiektem - da się zrobić cośCoJestObiektem instanceof Object a także zrobić generyka na nim List<TuWejdzieTylkoCośCoJestObiektem>.

No, że przetrzymuje tylko jedną wartość i jest na natywnie wmontowany w język. - ._.

W JavaScripcie w zasadzie wszystko jest wbudowane w język. W Javie prawie wszystko dostępne bezpośrednio przez programistę (bez refleksji) jest zakodowane w Javie w bibliotece standardowej. Python to takie coś pomiędzy - kolekcje ma wbudowane (tzn zaimplementowane w C), ale biblioteka standardowa to duża ilość kodu w Pythonie.

0

Testy mają być od siebie całkowicie odizolowane. Zabawę w przestawianie globalnego mutowalnego stanu oglądam w projekcie w firmie - poprzedni zespół wybrał framework webowy, który utrudniał wstrzykiwanie zależności i zamiast trzymać się jak najmniejszej ilości zła koniecznego, czyli globalnego mutowalnego stanu, programiści jeden za drugim potraktowali ten globalny mutowalny stan jako wzorzec projektowy. Efektem takiego podejścia są testy, które padają w losowych momentach i nie wiadomo od czego.

Ale ty mówisz o zpierniczonej architekturze i testach.
To ma niewiele do tego, że coś takiego jak ConnectionString jest ustalane tylko raz przy starcie aplikacji. Więc w teście integracyjnym, będzie zmieniany też tylko raz i nie ma to wpływu na testy asynchroniczne jak kolega wcześniej napisał. Problem może być, kiedy obiekt, który miesza w bazie jest statyczny ponieważ pobieranie/zapis do bazy to operacja nieatomowa.

0

W sensie, że dowolna instancja ma zwracać takie same wyniki? W takim razie albo ten string w środku będzie nieużywany, albo będzie miał wpływ na wyniki.

To czy obiekt jest bezstanowy oceniam po tym jak się zachowuje, a nie po tym co ma w środku.

0

To czy obiekt jest bezstanowy oceniam po tym jak się zachowuje, a nie po tym co ma w środku.

Jeśli to co ma w środku nie wpływa na jego zachowanie to po co ma mieć to coś w środku?

Ale ty mówisz o zpierniczonej architekturze i testach.
To ma niewiele do tego, że coś takiego jak ConnectionString jest ustalane tylko raz przy starcie aplikacji. Więc w teście integracyjnym, będzie zmieniany też tylko raz i nie ma to wpływu na testy asynchroniczne jak kolega wcześniej napisał. Problem może być, kiedy obiekt, który miesza w bazie jest statyczny ponieważ pobieranie/zapis do bazy to operacja nieatomowa.

Jeśli ten statyczny ConnectionString będzie wykorzystywany w testach do łączenia się z testową bazą to znaczy, że testy będą łączyć się z tą samą bazą i ją zmieniać (bo po co mi baza tylko do odczytu?), a więc polegać na współdzielonym mutowalnym globalnym stanie.

0

Jeśli ten statyczny ConnectionString będzie wykorzystywany w testach do łączenia się z testową bazą to znaczy, że testy będą łączyć się z tą samą bazą i ją zmieniać (bo po co mi baza tylko do odczytu?), a więc polegać na współdzielonym globalnym stanie.

Tak, ale tylko w kontekście konkretnych komponentów czy modułów. Potem baza zostanie zresetowana i ewentualnie przygotowana do innego testu.
A ty jak robisz do każdego testu inna baza z tym samym schematem ?.

Jeśli to co ma w środku nie wpływa na jego zachowanie to po co ma mieć to coś w środku?

Bo to, co jest w środku może być tylko do odczytu.

1

Tak, ale tylko w kontekście konkretnych komponentów czy modułów. Potem baza zostanie zresetowana i ewentualnie przygotowana do innego testu.
A ty jak robisz do każdego testu inna baza z tym samym schematem ?.

Masz parę rozwiązań:

  • wszystko robisz w jednej transakcji, którą potem odrzucasz
  • przygotowujesz sobie pool paru DB, i możesz testy odpalić na osobnych DB a następnie czyścić je; dzięki temu masz możliwość odpalenia paru testów równolegle

Inny przykład, może nie z DB, ale podobny - stanowa aplikacja do której idą zapytania: mockuję sobie S3. Jeśli całość konfiguracji byłaby tylko i wyłącznie w global storage, to musiałbym te testy odpalać synchronicznie, by sobie nie zawadzały nawzajem z danymi. Tak to dla każdego testu odpalam sobie process słuchający na innym porcie i wstrzykuję ten port do połączeń. Mogę sobie mieć N testów działających niezależnie i całkowicie izolowanych od reszty.

1

Tak, ale tylko w kontekście konkretnych komponentów czy modułów. Potem baza zostanie zresetowana i ewentualnie przygotowana do innego testu.
A ty jak robisz do każdego testu inna baza z tym samym schematem ?.

W jednym projekcie (który robiłem od zera) tak zrobiłem. Mogę w ten sposób wykorzystać wszystkie wątki procesora, więc trochę to redukuje narzut. Testy są natomiast w 100% odizolowane, więc jak któryś się sypnie to nie trzeba szukać przyczyny błędu w innych testach.

Bo to, co jest w środku może być tylko do odczytu.

Ta klasa jest stanowa czy nie? Dwie instancje spokojnie mogą zachowywać się różnie.

class MyClass {
  private final boolean state;
  public MyClass(boolean state) {
    this.state = state;
  }
  String process(String input) {
    return state ? input.toLowerCase() : input.toUpperCase();
  }
}

Jak zrobię DTO bez setterów to ma stan czy nie? Czyżbym wymyślił właśnie bezstanowe DTO?

0
Wibowit napisał(a):

Tak, ale tylko w kontekście konkretnych komponentów czy modułów. Potem baza zostanie zresetowana i ewentualnie przygotowana do innego testu.
A ty jak robisz do każdego testu inna baza z tym samym schematem ?.

W jednym projekcie (który robiłem od zera) tak zrobiłem. Mogę w ten sposób wykorzystać wszystkie wątki procesora, więc trochę to redukuje narzut. Testy są natomiast w 100% odizolowane, więc jak któryś się sypnie to nie trzeba szukać przyczyny błędu w innych testach.

Bo to, co jest w środku może być tylko do odczytu.

Ta klasa jest stanowa czy nie? Dwie instancje spokojnie mogą zachowywać się różnie.

class MyClass {
  private final boolean state;
  public MyClass(boolean state) {
    this.state = state;
  }
  String process(String input) {
    return state ? input.toLowerCase() : input.toUpperCase();
  }
}

Oczywiście, że stanowa.

tu masz bez stanową

class MyClass {
   private final boolean state = true;
   public MyClass() {
   }
   String process(String input) {
     return state ? input.toLowerCase() : input.toUpperCase();
   }
}

Ta też jest bezstanowa. Pomimo tego, że używa globalnego stanu, nie posiada własnego stanu, który ma wpływ na jej działanie, zachowanie.

class MyClass {
   String GetById(int id) {
     return new dataAccess.Get(id);
   }
}
0
._. napisał(a):

To ma niewiele do tego, że coś takiego jak ConnectionString jest ustalane tylko raz przy starcie aplikacji. Więc w teście integracyjnym, będzie zmieniany też tylko raz i nie ma to wpływu na testy asynchroniczne jak kolega wcześniej napisał. Problem może być, kiedy obiekt, który miesza w bazie jest statyczny ponieważ pobieranie/zapis do bazy to operacja nieatomowa.

Co to jest obiekt statyczny?

Wracając do tego connection stringa i jego wstrzykiwania - jeśli nie przez konstruktor, to jak własciwie: zapisane na stałe w kodzie klasy, setter, statyczne mutowalne pole tej klasy, jakaś zewnętrzna klasa z mutowalnym polem statycznym?

0
class MyClass {
   private final boolean state = true;
   public MyClass() {
   }
   String process(String input) {
     return state ? input.toLowerCase() : input.toUpperCase();
   }
}

Rzadko widuję taki kod. W takim przypadku pole state zamieniłbym na statyczne. Ale pobawię się dalej w kotka i myszkę. Czy taka klasa jest stanowa?

class MyClass {
   private final boolean state = System.currentTimeMillis() % 2 == 1;

   String process(String input) {
     return state ? input.toLowerCase() : input.toUpperCase();
   }
}

Jeśli tak, to dlaczego ta jest, a poprzednia nie?

0

Rzadko widuję taki kod. W takim przypadku pole state zamieniłbym na statyczne. Ale pobawię się dalej w kotka i myszkę. Czy taka klasa jest stanowa

A czy każda klasa, która posiada składowe będzie obiektem stanowym?

class MyClass {
   private final boolean state = new java.util.Random().nextBoolean();
 
   String process(String input) {
     return state ? input.toLowerCase() : input.toUpperCase();
   }
}

Dalej jest bezstanowa w kontekście publikowanego "interfejsu" metody.

1

Przypomnnę tylko twoją definicję:

Bezstanowść oznacza że dowolny klient może używać dowolnej instancji tego obiektu bez względu na historie konkretnej instanji.

Chyba jednak nie mogę używać dowolnej instancji.

0
Wibowit napisał(a):

Przypomnnę tylko twoją definicję:

Bezstanowść oznacza że dowolny klient może używać dowolnej instancji tego obiektu bez względu na historie konkretnej instanji.

Chyba jednak nie mogę używać dowolnej instancji.

A masz rację, bo jest Random().nextBoolean() ale dalej nie rozumiem co ci nie pasuje w tej definicji. Poza tym to definicja książkowa, a nie moja.
Chodzi głównie o stan, który coś zmienia w działaniu np. Serwisu, nie chodzi mi o np. "suche" DTO, które trudno nazwać "obiektem".

Co innego jak by tam było coś jak np. .NextRandomBoolean.

Java jest dla mnie dzika. Random().nextBoolean() za każdym razem daje inny wynik czy na przemian true, false

0
private final boolean state = System.currentTimeMillis() % 2 == 1;

Teraz tym bardziej jest bezstanowy. Niby co ma się zmienić? Czas przestanie płynąć, a losowa liczba przestanie być losowa.?

0

Masz bardzo dziwną definicję "stanowości" i "bez-stanowości". Dla mnie globalny stan jak najbardziej powoduje, że klasa jest stanowa. Zdecydowanie bardziej bezstanową metodą jest to co podał @Wibowit bo ta sama funkcja zawsze zwróci dokładnie ten sam wynik dla tych samych argumentów (this też jest argumentem). Co więcej, z racji tego, że zmienna jest prywatna i finalna powoduje, że cały obiekt jest niemutowalny, dzięki temu łatwo jest to przetestować. Twoje "przykłady bezstanowych klas" to jakieś koszmary testowalności i jeśli piszesz taki kod produkcyjnie, to proszę, podziel się gdzie, będę wiedział czego unikać.

0

@hauleth:

Pff. ty też mnie nie rozumiesz. To inaczej, powiedz mi czym jest operacja bezstanowa. Albo jeszcze inaczej czym jest serwis, bezstanowy.?

A od kiedy to klasa ma stan, myślałem że obiekt ma stan.

0

@._. czysta bezstanowość istnieje tylko na papierze, ale jeśli mielibyśmy się upierać, to dla mnie jest to taka funkcja (bo ciężko rozpatrywać to wtedy jako klasę), której wynik zależy tylko od argumentów wejściowych. Więc żaden z twoich przykładów jak na moje czymś takim nie jest, bo ich wynik zależy również od jakiegoś globalnego stanu, przykład @Wibowit tylko sprawia wrażenie, że od niego zależy, bo ta funkcja jest dokładnie tym samym co:

process(true, "data");

Tylko opakowuje nam booleana w jakiś nazwany kontener.

0

Obiekt bezstanowy to taki który zawiera tylko czyste funkcje (pure functions)

0
danek napisał(a):

Obiekt bezstanowy to taki który zawiera tylko czyste funkcje (pure functions)

I to taka różnica czy zapakuje wszystko w funkcje czy użyje np "constant" jako składowej.?

0

Co masz na myśli przez "constant"?

1

@._. tylko twoje constant nie są takie stałe, bo zmieniają się z każdą inicjalizacją obiektu. I ogólnie funkcje "czyste" powinny unikać bycia zależnymi nawet od stałych, oczywiście to nie zawsze będzie prawdą, bo np. areaOfCircle(double r) { return PI * r * r; } jest funkcją czystą, ale im mniej zależą od globalnego stanu (nie ważne czy to stałe czy zmienne), tym lepiej. No i oczywiście trzeba pamiętać, że konfiguracja nie jest stałą rzeczą. Należy to traktować jak globalną zmienną, więc jeśli funkcja zależy od globalnej konfiguracji, to nie jest czysta.

0

Macie screna z książki odnośnie tego o co mi chodzi mówiąc "operacja jest bezstanowa / obiekt jest bezstanowy"

3

Podany wycinek książki sugeruje, że chodzi o bezstanową usługę, a nie bezstanowy obiekt.

Jak zobaczyłem w którymś poprzednim poście stwierdzenie nie chodzi mi o np. "suche" DTO, które trudno nazwać "obiektem" to nasunęło mi się na myśl, że możesz czytać coś o DDD. DDD ma własny słownik pojęć, niekoniecznie zbieżny ze znaczeniami używanymi w innych kontekstach.

0
Wibowit napisał(a):

Podany wycinek książki sugeruje, że chodzi o bezstanową usługę, a nie bezstanowy obiekt.

Jak zobaczyłem w którymś poprzednim poście stwierdzenie nie chodzi mi o np. "suche" DTO, które trudno nazwać "obiektem" to nasunęło mi się na myśl, że możesz czytać coś o DDD. DDD ma własny słownik pojęć, niekoniecznie zbieżny ze znaczeniami używanymi w innych kontekstach.

Usługa to też obiekt. Pojęcia DDD w kontekście OOP wydają mi się jak najbardziej ok. A jeśli obiekt używa stanu/infromacji globalnych/statycznych oznacza, że posiada on stan?. Moim zdaniem nie bo to dwie inne bajki.

No a jaka dla ciebie definicja jest najbardziej ok i w jakim kontekście?

0

Jeśli obiekt mam jakąś zewnętrzną zależność to zgodnie z moją wcześniejszą (przykładową) definicją już nie jest bezstanowy. Ty tworzysz szczególny przypadek kiedy mogą być (czyli przekazywanie jakichś globalnych stałych), ale nie muszą być bezstanowe. Aczkolwiek mam wrażenie, że ta dyskusja idzie w złą stronę. Nie powinniśmy zastanawiać się jak pisać obiekty bezstanowe (i dywagować nad definicją), a zastanowić jak pisać kod łatwo testowalny i łatwy w utrzymaniu.

To o czym mówisz czyli używanie zewnętrznie ustalanych stałych brzmi o tyle źle, że przy testach itp musisz pamiętać je ustawić. Jeśli obiekt wymusił by je wprost po przez konstruktor dużo trudniej o tym zapomnieć.

No i ostatnia rzecz. Czy bezstanowość jest w ogóle pożądana? Nie ważniejsze jest, żeby starać się robić niemutowalne obiekty niżeli bezstanowe?

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