Kann die RxJava-Klasse Flowable berechtigterweise 460 Methoden haben?

14

Ich fange gerade mit RxJava an , Javas Implementierung von ReactiveX (auch bekannt als Rx und Reactive Extensions ). Was mich wirklich beeindruckt hat, war die enorme Größe der Flowable- Klasse von RxJava : Sie verfügt über 460 Methoden!

Um fair zu sein:

  • Es gibt viele Methoden, die überladen sind, was die Gesamtzahl der Methoden erheblich beeinträchtigt.

  • Vielleicht sollte diese Klasse aufgelöst werden, aber meine Kenntnisse und mein Verständnis von RxJava sind sehr begrenzt. Die Leute, die RxJava erstellt haben, sind sicherlich sehr schlau und sie können vermutlich gültige Argumente für die Entscheidung liefern , Flowable mit so vielen Methoden zu erstellen .

Auf der anderen Seite:

  • RxJava ist die Java-Implementierung von Microsofts Reactive Extensions , für die es nicht einmal eine Flowable- Klasse gibt. Es handelt sich also nicht darum , eine vorhandene Klasse blind zu portieren und in Java zu implementieren.

  • [ Update: Der vorherige kursive Punkt ist faktisch falsch: Die Observable- Klasse von Microsoft , die über 400 Methoden verfügt, wurde als Basis für die Observable- Klasse von RxJava verwendet , und Flowable ähnelt Observable , verarbeitet jedoch den Gegendruck für große Datenmengen. So das RxJava Team wurden eine vorhandene Klasse zu portieren. Dieser Beitrag hätte eher das ursprüngliche Design der Observable- Klasse von Microsoft als die Flowable-Klasse von RxJava in Frage stellen sollen .]

  • RxJava ist erst etwas älter als drei Jahre. Dies ist also kein Beispiel dafür, dass Code aufgrund mangelnder Kenntnisse über gute ( SOLID- ) Klassenentwurfsprinzipien falsch entworfen wurde (wie dies bei früheren Versionen von Java der Fall war).

Für eine so große Klasse wie Flowable scheint ihr Design von Natur aus falsch zu sein, aber vielleicht auch nicht. eine Antwort auf diese SE-Frage Was ist die Grenze für die Anzahl der Methoden einer Klasse? schlug vor, dass die Antwort " Haben Sie so viele Methoden, wie Sie brauchen ".

Es ist klar, dass es einige Klassen gibt, die zu Recht eine angemessene Anzahl von Methoden benötigen, um sie unabhängig von der Sprache zu unterstützen, da sie nicht leicht in kleinere Klassen zerfallen und eine angemessene Anzahl von Merkmalen und Attributen aufweisen. Zum Beispiel: Zeichenfolgen, Farben, Tabellenzellen, Datenbankergebnismengen und HTTP-Anforderungen. Vielleicht ein paar Dutzend Methoden für Klassen zu haben, um diese Dinge darzustellen, erscheint nicht unvernünftig.

Aber braucht Flowable wirklich 460 Methoden oder ist es so groß, dass es notwendigerweise ein Beispiel für schlechtes Klassendesign ist?

[Um klar zu sein: Diese Frage bezieht sich speziell auf die Flowable- Klasse von RxJava und nicht auf Objekte von Gott im Allgemeinen.]

Skomisa
quelle
1
@gnat Nun sicher verwandt, aber es ist kein Duplikat. Diese Frage war allgemein und meine Frage bezieht sich speziell auf die Flowable- Klasse von RxJava .
Skomisa
@skomisa Korrigieren Sie dann den Titel entsprechend Ihrer Frage.
Euphorischer
@Euphoric Punkt genommen.
Skomisa
1
Diese Frage ist sehr interessant und begründet. Ich würde jedoch vorschlagen, es leicht umzuformulieren, um einen weniger subjektiven Stil anzunehmen (wahrscheinlich aufgrund des anfänglichen Schocks ;-))
Christophe

Antworten:

14

TL; DL

Das Fehlen von Java-Sprachfunktionen im Vergleich zu C # sowie Überlegungen zur Erkennbarkeit haben uns veranlasst, Quell- und Zwischenoperatoren in große Klassen einzuteilen.

Design

Das ursprüngliche Rx.NET wurde in C # 3.0 entwickelt, das zwei entscheidende Merkmale aufweist: Erweiterungsmethoden und Teilklassen. Mit ersteren können Sie Instanzmethoden für andere Typen definieren, die dann Teil dieses Zieltyps zu sein scheinen, während Sie mit Teilklassen große Klassen in mehrere Dateien aufteilen können.

Keine dieser Funktionen war oder ist in Java vorhanden, daher mussten wir einen Weg finden, um RxJava so komfortabel wie möglich zu nutzen.

In RxJava gibt es zwei Arten von Operatoren: quellenähnlich, dargestellt durch statische Factory-Methoden, und zwischenähnlich, dargestellt durch Instanzmethoden. Ersteres könnte in jeder Klasse leben und somit über mehrere Versorgungsklassen verteilt sein. Letzteres setzt voraus, dass an einer Instanz gearbeitet wird. Konzeptionell könnten diese alle über statische Methoden mit dem Upstream als erstem Parameter ausgedrückt werden.

In der Praxis erschwert das Vorhandensein mehrerer Einstiegsklassen das Erkennen von Funktionen durch neue Benutzer (denken Sie daran, RxJava musste ein neues Konzept und Programmierparadigma in Java einführen) und die Verwendung dieser Zwischenoperatoren, was ein Albtraum ist. Aus diesem Grund hat das ursprüngliche Team das sogenannte flüssige API-Design entwickelt: Eine Klasse, die alle statischen Methoden und Instanzmethoden enthält und eine Quell- oder Verarbeitungsstufe für sich darstellt.

Aufgrund der erstklassigen Art von Fehlern, der Unterstützung von Parallelität und der Funktionsweise können alle Arten von Quellen und Transformationen in Bezug auf den reaktiven Fluss gefunden werden. Als sich die Bibliothek (und das Konzept) seit den Tagen von Rx.NET weiterentwickelten, kamen immer mehr Standardoperatoren hinzu, was die Anzahl der Methoden von Natur aus erhöhte. Dies führte zu zwei üblichen Beschwerden:

  • Warum gibt es so viele Methoden?
  • Warum gibt es keine Methode X, die mein ganz spezielles Problem löst?

Reaktive Operatoren zu schreiben ist eine schwierige Aufgabe, die nicht viele Menschen im Laufe der Jahre gemeistert haben. Die meisten typischen Bibliotheksbenutzer können keine Operatoren selbst erstellen (und oft ist es nicht wirklich notwendig, dass sie es versuchen). Dies bedeutet, dass wir von Zeit zu Zeit weitere Operatoren zum Standardsatz hinzufügen. Im Gegensatz dazu haben wir viel mehr Bediener abgelehnt, weil sie zu spezifisch sind oder einfach nur eine Annehmlichkeit, die ihr eigenes Gewicht nicht tragen kann.

Ich würde sagen, das Design von RxJava ist organisch gewachsen und entspricht nicht wirklich bestimmten Designprinzipien wie SOLID. Es wird hauptsächlich von der Verwendung und dem Gefühl der fließenden API bestimmt.

Andere Beziehungen zu Rx.NET

Ich bin Ende 2013 der RxJava-Entwicklung beigetreten. Soweit ich weiß, waren die ersten frühen 0.x-Versionen größtenteils eine Black-Box-Neuimplementierung, bei der die Namen und Signaturen von Rx.NET- ObservableBetreibern sowie einige Architekturentscheidungen wiederverwendet wurden. Dies betraf etwa 20% der Rx.NET-Betreiber. Die Hauptschwierigkeit war damals die Auflösung von Sprach- und Plattformunterschieden zwischen C # und Java. Mit viel Aufwand ist es uns gelungen, viele Operatoren zu implementieren, ohne den Quellcode von Rx.NET zu betrachten, und die komplizierteren zu portieren.

In diesem Sinne entsprach our Observablebis zu RxJava 0.19 den Methoden von Rx.NET IObservableund seinen Companion- ObservableErweiterungen. Das sogenannte Gegendruckproblem trat jedoch auf und RxJava 0.20 begann sich von Rx.NET auf Protokoll- und Architekturebene zu unterscheiden. Die verfügbaren Operatoren wurden erweitert, viele wurden auf den Gegendruck aufmerksam und wir führten neue Typen ein: Singleund Completablein der 1.x-Ära, die ab sofort keine Gegenstücke in Rx.NET haben.

Das Bewusstsein für den Gegendruck erschwert die Dinge erheblich und der 1.x Observablehat es nachträglich erhalten. Wir haben der Binärkompatibilität die Treue geschworen, sodass das Ändern des Protokolls und der API meistens nicht möglich war.

Es gab ein weiteres Problem mit der Architektur von Rx.NET: Eine synchrone Löschung ist nicht möglich, da dazu die DisposableRückgabe erforderlich ist , bevor der Bediener mit der Ausführung beginnt. Quellen wie Rangewaren jedoch eifrig und kehren nicht zurück, bis sie fertig sind. Dieses Problem kann gelöst werden, indem ein Disposablein das injiziert wird, Observeranstatt eines von zurückzugeben subscribe().

RxJava 2.x wurde überarbeitet und von Grund auf neu implementiert . Wir haben einen separaten Typ, Flowableder den Gegendruck berücksichtigt und die gleichen Operatoren bietet wie Observable. Observableunterstützt keinen Gegendruck und ist etwas äquivalent zu Rx.NET Observable. Intern geben alle reaktiven Typen ihren Widerrufscode an ihre Verbraucher weiter, sodass der synchrone Widerruf effizient funktioniert.

akarnokd
quelle
10

Ich gebe zwar zu, dass ich mit der Bibliothek nicht vertraut bin, habe mir aber die fragliche Flowable-Klasse angesehen und es scheint, als würde sie sich wie eine Art Drehscheibe verhalten . Mit anderen Worten, es ist eine Klasse, die Eingaben validieren und Aufrufe im Projekt entsprechend verteilen soll.

Diese Klasse würde daher nicht wirklich als ein Gottobjekt betrachtet, da ein Gottobjekt eines ist, das versucht, alles zu tun. Das hat wenig mit Logik zu tun. In Bezug auf die Einzelverantwortung kann gesagt werden, dass die einzige Aufgabe der Klasse darin besteht, die Arbeit in der gesamten Bibliothek zu delegieren.

Daher würde eine solche Klasse natürlich eine Methode für jede mögliche Aufgabe erfordern, die Sie Flowablein diesem Kontext von einer Klasse benötigen würden . Sie sehen den gleichen Mustertyp bei der jQuery-Bibliothek in Javascript, wo die Variable $alle Funktionen und Variablen enthält, die zum Ausführen von Aufrufen auf die Bibliothek erforderlich sind. Bei jQuery ist der Code jedoch nicht einfach delegiert, sondern auch ein gutes Stück Logik wird innerhalb von ausgeführt.

Ich denke, Sie sollten darauf achten, eine Klasse wie diese zu erstellen, aber sie hat ihren Platz, solange sich der Entwickler daran erinnert, dass sie nur ein Hub ist und sich daher nicht langsam in ein Gott-Objekt verwandelt.

Neil
quelle
2
Entschuldigung für das Entfernen der Akzeptanz Ihrer Antwort, die sowohl hilfreich als auch aufschlussreich war, aber Akarnokds nachfolgende Antwort stammte von jemandem aus dem RxJava-Team!
Skomisa
@skomisa Ich könnte auf nichts mehr hoffen, wenn es meine eigene Frage wäre! Kein Vergehen! :)
Neil
7

Das RX-Äquivalent von .NET Flowableist Observable . Es hat auch alle diese Methoden, aber sie sind statisch und können als Erweiterungsmethoden verwendet werden . Der Hauptpunkt von RX ist, dass die Komposition über eine flüssige Oberfläche geschrieben wird .

Damit Java jedoch über eine flüssige Schnittstelle verfügt, müssen diese Metoden Instanzmethoden sein, da statische Methoden nicht gut zusammengesetzt sind und es keine Erweiterungsmethoden gibt, mit denen statische Methoden zusammengesetzt werden können. In der Praxis könnten all diese Methoden zu statischen Methoden gemacht werden, sofern Sie keine fließende Schnittstellensyntax verwenden.

Euphorisch
quelle
3
Das fließende Schnittstellenkonzept ist meiner Meinung nach eine Problemumgehung für Spracheinschränkungen. Tatsächlich bauen Sie eine Sprache mit einer Klasse auf Java auf. Wenn Sie darüber nachdenken, wie viele Funktionen selbst in einer einfachen Programmiersprache vorhanden sind, und nicht alle überladenen Varianten zählen, wird es ziemlich einfach zu erkennen, wie Sie zu diesen vielen Methoden kommen. Die Funktionsmerkmale von Java 8 können viele der Probleme lösen, die zu dieser Art von Design führen, und moderne Sprachen wie Kotlin sind in der Lage, die gleichen Vorteile zu erzielen, ohne dass eine Methodenverkettung erforderlich ist.
JimmyJames
Dank deines Beitrags habe ich tiefer gegraben und es scheint, dass .NETs RX's Observable ≈ von RxJava's Observable ist, also war eine Prämisse meiner Frage falsch. Ich habe das OP entsprechend aktualisiert. Außerdem RxJava's Flowable (Observable + einige zusätzliche Methoden zur Parallelisierung und Handhabung des Gegendrucks).
Skomisa
@skomisa Java Observable hat auch Hunderte von Methoden. So können Sie die beiden vergleichen. Der Hauptunterschied ist jedoch, dass .NET Observable statisch ist und alle Methoden statisch sind. Während Java's nicht ist. Und das ist ein großer Unterschied. In der Praxis verhalten sie sich jedoch genauso.
Euphoric