La classe RxJava Flowable peut-elle légitimement avoir 460 méthodes?

14

Je viens de commencer avec RxJava , l'implémentation Java de ReactiveX (également connu sous le nom de Rx et Reactive Extensions ). Quelque chose qui m'a vraiment frappé était la taille massive de de RxJava Flowable classe : il a 460 méthodes!

Être juste:

  • De nombreuses méthodes sont surchargées, ce qui augmente considérablement le nombre total de méthodes.

  • Peut-être que cette classe devrait être interrompue, mais ma connaissance et ma compréhension de RxJava sont très limitées. Les gens qui ont créé RxJava sont sûrement très intelligents, et ils peuvent sans doute proposer des arguments valables pour choisir de créer Flowable avec autant de méthodes.

D'autre part:

  • RxJava est l'implémentation Java des extensions réactives de Microsoft , et qui n'a même pas de classe Flowable , il ne s'agit donc pas de porter aveuglément une classe existante et de l'implémenter en Java.

  • [ Mise à jour: le point précédent en italique est incorrect dans les faits: la classe Observable de Microsoft , qui compte plus de 400 méthodes, a été utilisée comme base de la classe Observable de RxJava , et Flowable est similaire à Observable mais gère la contre-pression pour de gros volumes de données. Ainsi , l'équipe RxJava ont été portage d' une classe existante. Ce poste aurait dû être contester la conception originale de la Observable classe par Microsoft plutôt que de RxJava Flowable classe.]

  • RxJava n'a qu'un peu plus de 3 ans, donc ce n'est pas un exemple de code mal conçu en raison d'un manque de connaissances sur les bons principes de conception de classe ( SOLID ) (comme c'était le cas avec les premières versions de Java).

Pour une classe aussi grande que Flowable, sa conception semble intrinsèquement erronée, mais peut-être pas; une réponse à cette question SE Quelle est la limite du nombre de méthodes d'une classe? a suggéré que la réponse soit " Avoir autant de méthodes que nécessaire ".

Il est clair que certaines classes ont légitimement besoin d'un bon nombre de méthodes pour les prendre en charge indépendamment de la langue, car elles ne se décomposent pas facilement en quelque chose de plus petit et elles ont un bon nombre de caractéristiques et d'attributs. Par exemple: chaînes, couleurs, cellules de feuille de calcul, jeux de résultats de base de données et requêtes HTTP. Avoir peut-être quelques dizaines de méthodes pour que les classes représentent ces choses ne semble pas déraisonnable.

Mais Flowable a-t-il vraiment besoin de 460 méthodes, ou est-ce si énorme qu'il s'agit nécessairement d'un exemple de mauvaise conception de classe?

[Pour être clair: cette question concerne spécifiquement de RxJava Flowable classe plutôt que des objets Dieu en général.]

skomisa
la source
1
@gnat Eh bien certainement lié, mais ce n'est pas un doublon. Cette question était générique, et ma question concerne spécifiquement de RxJava Flowable classe.
skomisa
@skomisa Fixez ensuite le titre en fonction de votre question.
Euphoric
@Euphoric Point pris.
skomisa du
1
Cette question est très intéressante et fondée. Cependant, je suggère de le reformuler légèrement afin d'adopter un style moins subjectif (probablement dû au choc initial ;-))
Christophe

Réponses:

14

TL; DL

Le manque de fonctionnalités de langage de Java par rapport à C # ainsi que les considérations de découvrabilité nous ont fait placer les opérateurs source et intermédiaires dans de grandes classes.

Conception

Le Rx.NET d'origine a été développé en C # 3.0 qui a deux fonctionnalités cruciales: les méthodes d'extension et les classes partielles. Le premier vous permet de définir des méthodes d'instance sur d'autres types qui semblent alors faire partie de ce type cible tandis que les classes partielles vous permettent de diviser les grandes classes en plusieurs fichiers.

Aucune de ces fonctionnalités n'était ou n'est présente dans Java, par conséquent, nous avons dû trouver un moyen d'utiliser RxJava de manière suffisamment pratique.

Il existe deux types d'opérateurs dans RxJava: de type source représentés par des méthodes d'usine statiques et de type intermédiaire représentés par des méthodes d'instance. Les premiers pouvaient vivre dans n'importe quelle classe et donc ils auraient pu être répartis sur plusieurs classes d'utilité. Ce dernier nécessite de travailler sur une instance. En théorie, tout cela pourrait être exprimé par des méthodes statiques avec l'amont comme premier paramètre.

En pratique, cependant, le fait d'avoir plusieurs classes d'entrée rend la découverte de fonctionnalités par les nouveaux utilisateurs peu pratique (rappelez-vous, RxJava a dû apporter un nouveau concept et paradigme de programmation en Java) ainsi que l'utilisation de ces opérateurs intermédiaires un peu comme un cauchemar. Par conséquent, l'équipe d'origine a proposé la conception de l'API dite fluide: une classe qui contient toutes les méthodes statiques et d'instance et représente une source ou une étape de traitement à elle seule.

En raison de la nature de première classe des erreurs, de la prise en charge de la concurrence et de la nature fonctionnelle, on peut trouver toutes sortes de sources et de transformations concernant le flux réactif. Comme la bibliothèque (et le concept) ont évolué depuis l'époque de Rx.NET, de plus en plus d'opérateurs standard ont été ajoutés, ce qui, par nature, a augmenté le nombre de méthodes. Cela a conduit à deux plaintes habituelles:

  • Pourquoi y a-t-il tant de méthodes?
  • Pourquoi n'y a-t-il pas une méthode X qui résout mon problème très particulier?

Écrire des opérateurs réactifs est une tâche difficile que peu de gens maîtrisent au fil des ans; la plupart des utilisateurs de bibliothèques typiques ne peuvent pas créer d'opérateurs par eux-mêmes (et il n'est souvent pas vraiment nécessaire d'essayer). Cela signifie que de temps en temps, nous ajoutons d'autres opérateurs à l'ensemble standard. En revanche, nous avons rejeté beaucoup plus d'opérateurs en raison de leur caractère trop spécifique ou simplement d'une commodité qui ne peut pas tirer son propre poids.

Je dirais que la conception de RxJava a grandi de manière organique et pas vraiment selon certains principes de conception tels que SOLID. Il est principalement motivé par l'utilisation et la convivialité de son API fluide.

Autres relations avec Rx.NET

J'ai rejoint le développement RxJava fin 2013. Pour autant que je sache, les premières versions initiales de 0.x étaient en grande partie une réimplémentation de boîte noire où les noms et signatures des Observableopérateurs Rx.NET ainsi que quelques décisions architecturales ont été réutilisés. Cela impliquait environ 20% des opérateurs Rx.NET. La principale difficulté à l'époque était la résolution des différences de langage et de plate-forme entre C # et Java. Avec beaucoup d'efforts, nous avons réussi à implémenter de nombreux opérateurs sans regarder le code source de Rx.NET et nous avons porté les plus compliqués.

En ce sens, jusqu'à RxJava 0.19, notre Observableétait équivalent à Rx.NET IObservableet à ses Observableméthodes d'extension associées . Cependant, le soi-disant problème de contre-pression est apparu et RxJava 0.20 a commencé à diverger de Rx.NET au niveau du protocole et de l'architecture. Les opérateurs disponibles ont été étendus, beaucoup sont devenus sensibles à la contre-pression et nous avons introduit de nouveaux types: Singleet Completableà l'ère 1.x, qui n'ont pas d'homologues dans Rx.NET pour l'instant.

La prise de conscience de la contre-pression complique considérablement les choses et le 1.x l'a Observablereçue après coup. Nous avons juré allégeance à la compatibilité binaire, donc changer le protocole et l'API n'était pas possible.

Il y avait un autre problème avec l'architecture de Rx.NET: l'annulation synchrone n'est pas possible car pour cela, il faut que le Disposablesoit retourné avant que l'opérateur ne commence à s'exécuter. Cependant, des sources comme celles qui Rangeétaient impatientes ne reviennent pas avant d'avoir fini. Ce problème peut être résolu en injectant un Disposabledans le Observerau lieu d'en renvoyer un subscribe().

RxJava 2.x a été repensé et réimplémenté à partir de zéro dans ce sens. Nous avons un type distinct, sensible à la contre-pression, Flowablequi offre le même ensemble d'opérateurs que Observable. Observablene prend pas en charge la contre-pression et est quelque peu équivalent à Rx.NET Observable. En interne, tous les types réactifs injectent leur poignée d'annulation à leurs consommateurs, permettant à l'annulation synchrone de fonctionner efficacement.

akarnokd
la source
10

Bien que j'admette que je ne connais pas la bibliothèque, j'ai jeté un coup d'œil à la classe Flowable en question et il semble qu'elle agisse comme une sorte de hub. En d'autres termes, il s'agit d'une classe destinée à valider l'entrée et à répartir les appels en conséquence dans le projet.

Cette classe ne serait donc pas vraiment considérée comme un objet divin car un objet divin est celui qui tente de tout faire. Cela fait très peu en termes de logique. En termes de responsabilité unique, le seul travail de la classe pourrait être de déléguer le travail dans toute la bibliothèque.

Donc, naturellement, une telle classe nécessiterait une méthode pour chaque tâche possible dont vous auriez besoin d'une Flowableclasse dans ce contexte. Vous voyez le même type de modèle avec la bibliothèque jQuery en javascript, où la variable $a toutes les fonctions et variables nécessaires pour effectuer des appels sur la bibliothèque, bien que dans le cas de jQuery, le code n'est pas simplement délégué mais aussi une bonne logique est exécuté à l'intérieur.

Je pense que vous devriez faire attention à créer une classe comme celle-ci, mais elle a sa place tant que le développeur se souvient qu'il ne s'agit que d'un concentrateur et qu'il ne se convertit donc pas lentement en un objet divin.

Neil
la source
2
Toutes mes excuses pour avoir retiré l'acceptation de votre réponse, ce qui était à la fois utile et instructif, mais la réponse subséquente d'Akarnokd a été de la part d'un membre de l'équipe RxJava!
skomisa
@skomisa Je ne pourrais rien espérer de plus si c'était ma propre question! Aucune infraction prise! :)
Neil
7

L'équivalent de RX de .NET Flowableest Observable . Il possède également toutes ces méthodes, mais elles sont statiques et utilisables comme méthodes d'extension . Le point principal de RX est que la composition est écrite en utilisant une interface fluide .

Mais pour que Java ait une interface fluide, il a besoin que ces métodes soient des méthodes d'instance, car les méthodes statiques ne composeraient pas bien et il n'a pas de méthodes d'extension pour rendre les méthodes statiques composables. Donc, dans la pratique, toutes ces méthodes peuvent devenir des méthodes statiques, à condition que vous n'ayez pas à utiliser une syntaxe d'interface fluide.

Euphorique
la source
3
Le concept d'interface fluide est, à mon humble avis, une solution de contournement pour les limitations linguistiques. En effet, vous construisez un langage en utilisant une classe au-dessus de Java. Si vous pensez au nombre de fonctionnalités dans un langage de programmation même simple et ne comptez pas toutes les variantes surchargées, il devient assez facile de voir comment vous vous retrouvez avec ces nombreuses méthodes. Les caractéristiques fonctionnelles de Java 8 peuvent résoudre de nombreux problèmes qui conduisent à ce type de conception et les langages contemporains tels que Kotlin se déplacent pour pouvoir obtenir le même type d'avantages sans avoir besoin de chaîner les méthodes.
JimmyJames
Grâce à votre message, j'ai creusé plus profondément et il semble que l'observable RX de .NET soit is à l'observable de RxJava, donc une prémisse de ma question était incorrecte. J'ai mis à jour l'OP en conséquence. Aussi, Flowable ≈ de RxJava (Observable + quelques méthodes supplémentaires pour paralléliser et gérer la contre-pression).
skomisa
@skomisa Java's Observable possède également des centaines de méthodes. Vous pouvez donc comparer les deux. Mais la principale différence est que l'Observable de .NET est statique et que toutes les méthodes sont statiques. Alors que Java ne l'est pas. Et c'est une énorme différence. Mais en pratique, ils se comportent de la même manière.
Euphoric