Czy klasa RxJava Flowable może posiadać 460 metod?

14

Właśnie zaczynam od RxJava , implementacji Java ReactiveX (znanej również jako Rx i Reactive Extensions ). Coś, co naprawdę uderzyło mnie był ogromny rozmiar RxJava w płynnej klasy : ma 460 sposobów!

Aby być uczciwym:

  • Istnieje wiele metod, które są przeciążone, co znacznie zwiększa całkowitą liczbę metod.

  • Być może ta klasa powinna zostać zerwana, ale moja wiedza i rozumienie RxJava jest bardzo ograniczone. Ludzie, którzy stworzyli RxJava, są z pewnością bardzo sprytni i prawdopodobnie mogą przedstawić uzasadnione argumenty przemawiające za stworzeniem Flowalnego przy użyciu tak wielu metod.

Z drugiej strony:

  • RxJava to implementacja Java Reactive Extensions firmy Microsoft , która nie ma nawet klasy Flowable , więc nie jest to przypadek ślepego przenoszenia istniejącej klasy i implementacji jej w Javie.

  • [ Aktualizacja: Dotychczasowy punkt kursywą jest niezgodny ze stanem faktycznym: Microsoft Obserwowalne klasa, która ma ponad 400 metod, został użyty jako podstawa do RxJava za obserwowalne klasy i płynny jest podobna do Obserwowalne ale uchwyty Backpressure dla dużych ilości danych. Więc zespołu RxJava były przeniesieniem istniejącej klasy. Ten post powinien być wyzwaniem oryginalną konstrukcję obserwowalnych klasie przez Microsoft zamiast RxJava w płynnej klasie.]

  • RxJava ma niewiele ponad 3 lata, więc nie jest to przykład błędnego zaprojektowania kodu z powodu braku wiedzy na temat dobrych ( SOLID ) zasad projektowania klas (tak jak miało to miejsce we wczesnych wersjach Javy).

Dla klasy tak dużej jak Flowble jej konstrukcja wydaje się z natury niewłaściwa, ale może nie; jedna odpowiedź na to pytanie SE Jakie jest ograniczenie liczby metod klasowych? zasugerował, że odpowiedź brzmi „ Stosuj tyle metod, ile potrzebujesz ”.

Oczywiście są pewne klasy, które zgodnie z prawem potrzebują sporej liczby metod do ich obsługi bez względu na język, ponieważ nie rozpadają się one na nic mniejszego i mają spore liczby cech i atrybutów. Na przykład: ciągi, kolory, komórki arkusza kalkulacyjnego, zestawy wyników bazy danych i żądania HTTP. Posiadanie może kilkudziesięciu metod reprezentowania tych klas przez klasy nie wydaje się nierozsądne.

Ale czy Flowable naprawdę potrzebuje 460 metod, czy też jest tak ogromny, że jest koniecznie przykładem złej klasy projektu?

[Żeby było jasne: to pytanie dotyczy w szczególności RxJava w płynnej klasy zamiast obiektów Boga w ogóle.]

skomisa
źródło
1
@gnat Cóż, na pewno powiązane, ale to nie jest duplikat. Że pytanie było ogólne, i moje pytanie dotyczy w szczególności RxJava w płynnej klasie.
skomisa
@skomisa Następnie popraw tytuł, aby pasował do twojego pytania.
Euforyczny
@Euphoric Point podjęte.
skomisa
1
To pytanie jest bardzo interesujące i uzasadnione. Proponuję jednak nieco przeredagować, aby zastosować mniej subiektywny styl (prawdopodobnie ze względu na początkowy szok ;-))
Christophe

Odpowiedzi:

14

TL; DL

Brak funkcji językowych Java w porównaniu do C #, a także względy wykrywalności sprawiły, że umieściliśmy operatory źródłowe i pośrednie w dużych klasach.

Projekt

Oryginalny Rx.NET został opracowany w C # 3.0, który ma dwie kluczowe cechy: metody rozszerzenia i klasy częściowe. Ten pierwszy pozwala zdefiniować metody instancji dla innych typów, które wydają się być częścią tego typu docelowego, podczas gdy klasy częściowe pozwalają na podział dużych klas na wiele plików.

Żadna z tych funkcji nie była ani nie jest obecna w Javie, dlatego musieliśmy znaleźć sposób, aby RxJava była wystarczająco wygodna w użyciu.

Istnieją dwa rodzaje operatorów w RxJava: podobne do źródła reprezentowane przez statyczne metody fabryczne i podobne do pośrednich reprezentowane przez metody instancji. Te pierwsze mogłyby żyć w dowolnej klasie, a zatem mogły być rozdzielone na wiele klas użyteczności. Ten ostatni wymaga opracowania instancji. Koncepcyjnie, wszystko to można wyrazić metodami statycznymi, stosując jako pierwszy parametr upstream.

W praktyce jednak posiadanie wielu klas wejściowych sprawia, że ​​odkrywanie funkcji przez nowych użytkowników jest niewygodne (pamiętaj, że RxJava musiał wprowadzić nową koncepcję i paradygmat programowania do Java), a także korzystanie z tych operatorów pośrednich w pewnym sensie koszmar. Dlatego oryginalny zespół opracował tak zwany płynny projekt API: jedna klasa, która przechowuje wszystkie metody statyczne i instancji i sama stanowi źródło lub etap przetwarzania.

Ze względu na pierwszorzędną naturę błędów, obsługę współbieżności i charakter funkcjonalny, można wymyślić wszelkiego rodzaju źródła i transformacje dotyczące przepływu reaktywnego. W miarę ewolucji biblioteki (i koncepcji) od czasów Rx.NET, dodawano coraz więcej standardowych operatorów, co z natury zwiększyło liczbę metod. Doprowadziło to do dwóch zwykłych skarg:

  • Dlaczego jest tak wiele metod?
  • Dlaczego nie ma metody X, która rozwiązałaby mój szczególny problem?

Pisanie reaktywnych operatorów jest trudnym zadaniem, którego nie opanowało wiele osób na przestrzeni lat; najbardziej typowi użytkownicy bibliotek nie mogą sami tworzyć operatorów (i często nie jest to konieczne, aby próbowali). Oznacza to, że od czasu do czasu dodajemy kolejnych operatorów do standardowego zestawu. W przeciwieństwie do tego odrzuciliśmy wiele innych operatorów ze względu na ich zbyt dużą dokładność lub po prostu wygodę, która nie jest w stanie znieść własnej wagi.

Powiedziałbym, że projekt RxJava rozwijał się organicznie, a nie tak naprawdę, zgodnie z pewnymi zasadami projektowania, takimi jak SOLID. Opiera się głównie na użyciu i wyczuciu płynnego API.

Inne relacje z Rx.NET

Dołączyłem do rozwoju RxJava pod koniec 2013 roku. O ile wiem, początkowe wczesne wersje 0.x były w dużej mierze ponowną implementacją czarnej skrzynki, w której ponownie wykorzystano nazwy i podpisy Observableoperatorów Rx.NET , a także kilka decyzji architektonicznych. Dotyczyło to około 20% operatorów Rx.NET. Główną trudnością w tamtym czasie była rozdzielczość różnic językowych i platformowych między C # a Javą. Z wielkim wysiłkiem udało nam się wdrożyć wielu operatorów bez patrzenia na kod źródłowy Rx.NET i przenieśliśmy tych bardziej skomplikowanych.

W tym sensie, aż do RxJava 0.19, nasz Observablebył równoważny z Rx.NET IObservablei jego Observablemetodami rozszerzenia towarzyszącego . Pojawił się jednak tak zwany problem ciśnienia wstecznego i RxJava 0.20 zaczął odbiegać od Rx.NET na poziomie protokołu i architektury. Dostępne operatory zostały rozszerzone, wielu stało się świadomych przeciwciśnienia i wprowadziliśmy nowe typy: Singleoraz Completablew erze 1.x, które nie mają obecnie odpowiedników w Rx.NET.

Świadomość przeciwciśnienia znacznie komplikuje sytuację, a 1.x Observableotrzymał ją po namyśle. Przysięgaliśmy wierność kompatybilności binarnej, więc zmiana protokołu i interfejsu API była w większości niemożliwa.

Wystąpił inny problem z architekturą Rx.NET: synchroniczne anulowanie nie jest możliwe, ponieważ w tym Disposablecelu należy zwrócić, zanim operator zacznie wykonywać. Jednak źródła takie jak Rangechętne i nie wracają, dopóki nie skończą. Ten problem można rozwiązać, wstrzykując Disposabledo niego Observerzamiast zwracając jeden z subscribe().

RxJava 2.x został przeprojektowany i ponownie wdrożony od zera według tych zasad . Mamy osobny typ uwzględniający przeciwciśnienie, Flowablektóry oferuje ten sam zestaw operatorów co Observable. Observablenie obsługuje przeciwciśnienia i jest nieco równoważny z Rx.NET Observable. Wewnętrznie wszystkie typy reaktywne wstrzykują swój uchwyt anulowania do swoich klientów, umożliwiając wydajne synchroniczne anulowanie.

akarnokd
źródło
10

Przyznaję, że nie jestem zaznajomiony z biblioteką, ale przyjrzałem się omawianej klasie Flowable i wygląda na to, że działa ona jak swego rodzaju centrum. Innymi słowy, jest to klasa przeznaczona do sprawdzania poprawności danych wejściowych i odpowiedniej dystrybucji wywołań w projekcie.

Dlatego klasa ta nie byłaby tak naprawdę uważana za obiekt Boga, ponieważ obiekt Boga jest tym, który próbuje zrobić wszystko. To robi bardzo niewiele pod względem logiki. Jeśli chodzi o pojedynczą odpowiedzialność, można powiedzieć, że jedynym zadaniem klasy jest delegowanie pracy w całej bibliotece.

Naturalnie taka klasa wymagałaby metody dla każdego możliwego zadania wymaganego od Flowableklasy w tym kontekście. Widzisz ten sam typ wzorca z biblioteką jQuery w javascript, gdzie zmienna $ma wszystkie funkcje i zmienne niezbędne do wykonywania wywołań w bibliotece, choć w przypadku jQuery kod nie jest po prostu delegowany, ale także sporo logiki jest uruchamiany w ramach.

Myślę, że powinieneś być ostrożny, aby stworzyć taką klasę, ale ma ona swoje miejsce, dopóki programista pamięta, że ​​jest to tylko hub, a zatem nie przekształca się powoli w obiekt boga.

Neil
źródło
2
Przepraszam za usunięcie akceptacji twojej odpowiedzi, która była zarówno pomocna, jak i pouczająca, ale późniejszą odpowiedzią akarnokd była osoba z zespołu RxJava!
skomisa
@skomisa Mógłbym mieć nadzieję na nic więcej, gdyby to było moje własne pytanie! Bez przestępstwa! :)
Neil
7

Odpowiednik RX platformy .NET Flowablejest obserwowalny . Ma także wszystkie te metody, ale są one statyczne i można je stosować jako metody rozszerzenia . Głównym punktem RX jest to, że kompozycja jest napisana przy użyciu płynnego interfejsu .

Ale aby Java miała płynny interfejs, muszą one być metodami instancyjnymi, ponieważ metody statyczne nie komponowałyby się ładnie i nie miały metod rozszerzających, aby metody statyczne można było komponować. W praktyce więc wszystkie te metody mogą być metodami statycznymi, o ile nie ma potrzeby płynnej składni interfejsu.

Euforyk
źródło
3
Koncepcja płynnego interfejsu stanowi, według IMHO, obejście ograniczeń językowych. W efekcie budujesz język za pomocą klasy opartej na Javie. Jeśli pomyślisz o tym, ile funkcji ma nawet prosty język programowania i nie policzysz wszystkich przeciążonych wariantów, łatwo będzie zobaczyć, jak skończysz z tak wieloma metodami. Funkcje funkcjonalne Java 8 mogą rozwiązać wiele problemów, które prowadzą do tego rodzaju projektowania, a współczesne języki, takie jak Kotlin, przenoszą się, aby uzyskać takie same korzyści bez konieczności łączenia łańcuchów metod.
JimmyJames
Dzięki Twojemu postowi zagłębiłem się głębiej i wydaje się, że .X's RX's Observable to ≈ do RxJava's Observable, więc przesłanka mojego pytania była nieprawidłowa. Odpowiednio zaktualizowałem PO. Ponadto, RxJava's Flowable ≈ (Obserowalny + kilka dodatkowych metod równoległego i obsługi przeciwciśnienia).
skomisa
@ Obomisa Java's Observable ma również setki metod. Możesz więc porównać oba. Ale główna różnica polega na tym, że .NET's Observable jest statyczny, a wszystkie metody są statyczne. Podczas gdy Java nie jest. I to jest ogromna różnica. Ale w praktyce zachowują się tak samo.
Euforyczny