¿Puede la clase RxJava Flowable legítimamente tener 460 métodos?

14

Recién estoy comenzando con RxJava , la implementación de ReactiveX en Java (también conocida como Rx y Reactive Extensions ). Algo que realmente me llamó la atención fue el enorme tamaño de la RxJava Fluido clase : tiene 460 métodos!

Para ser justo:

  • Hay muchos métodos que están sobrecargados, lo que aumenta significativamente el número total de métodos.

  • Quizás esta clase debería dividirse, pero mi conocimiento y comprensión de RxJava es muy limitado. Las personas que crearon RxJava seguramente son muy inteligentes, y presumiblemente pueden ofrecer argumentos válidos para elegir crear Flowable con tantos métodos.

Por otra parte:

  • RxJava es la implementación de Java de las Extensiones reactivas de Microsoft , y eso ni siquiera tiene una clase Flowable , por lo que no se trata de portar a ciegas una clase existente e implementarla en Java.

  • [ Actualización: el punto anterior en cursiva es incorrecto: la clase Observable de Microsoft , que tiene más de 400 métodos, se utilizó como base para la clase Observable de RxJava , y Flowable es similar a Observable pero maneja la contrapresión para grandes volúmenes de datos. Entonces, el equipo de RxJava estaba portando una clase existente. Este post debería haber sido un reto el diseño original de la observable clase por Microsoft en lugar de RxJava de Fluido clase.]

  • RxJava tiene solo poco más de 3 años, por lo que este no es un ejemplo de código mal diseñado debido a la falta de conocimiento sobre principios de diseño de clase buenos ( SOLIDOS ) (como fue el caso con las primeras versiones de Java).

Para una clase tan grande como Flowable, su diseño parece intrínsecamente incorrecto, pero tal vez no; una respuesta a esta pregunta SE ¿ Cuál es el límite para el número de métodos de una clase? sugirió que la respuesta es " Tenga tantos métodos como necesite ".

Claramente, hay algunas clases que legítimamente necesitan una buena cantidad de métodos para apoyarlas, independientemente del idioma, porque no se dividen fácilmente en nada más pequeño y tienen una buena cantidad de características y atributos. Por ejemplo: cadenas, colores, celdas de hojas de cálculo, conjuntos de resultados de bases de datos y solicitudes HTTP. Tener unas pocas docenas de métodos para que las clases representen esas cosas no parece irrazonable.

¿Pero Flowable realmente necesita métodos 460, o es tan grande que es necesariamente un ejemplo de diseño de mala clase?

[Para que quede claro: esta pregunta se refiere específicamente a la RxJava Fluido clase en lugar de objetos de Dios en general.]

skomisa
fuente
1
@gnat Bueno, ciertamente relacionado, pero no es un duplicado. Esa pregunta era genérica, y mi pregunta se refiere específicamente a la RxJava Fluido clase.
skomisa
@skomisa Luego arregla el título para que se ajuste a tu pregunta.
Eufórico el
@ Punto de euforia tomado.
skomisa
1
Esta pregunta es muy interesante y fundada. Sin embargo, sugeriría que lo reformule un poco para adoptar un estilo menos subjetivo (probablemente debido al shock inicial ;-))
Christophe

Respuestas:

14

TL; DL

La falta de características del lenguaje de Java en comparación con C #, así como las consideraciones de capacidad de descubrimiento, nos hicieron poner a los operadores de origen e intermedios en grandes clases.

Diseño

El Rx.NET original se desarrolló en C # 3.0 que tiene dos características cruciales: métodos de extensión y clases parciales. El primero le permite definir métodos de instancia en otros tipos que luego parecen ser parte de ese tipo de destino, mientras que las clases parciales le permiten dividir clases grandes en múltiples archivos.

Ninguna de estas características estaban o están presentes en Java, por lo tanto, tuvimos que encontrar una manera de hacer que RxJava sea lo suficientemente conveniente.

Hay dos tipos de operadores en RxJava: tipo fuente representado por métodos de fábrica estáticos y tipo intermedio representado por métodos de instancia. Los primeros podrían vivir en cualquier clase y, por lo tanto, podrían haberse distribuido a lo largo de múltiples clases de utilidad. Este último requiere una instancia para trabajar. En concepto, todo esto podría expresarse a través de métodos estáticos con el flujo ascendente como primer parámetro.

Sin embargo, en la práctica, tener múltiples clases de entrada hace que el descubrimiento de características por parte de nuevos usuarios sea un inconveniente (recuerde, RxJava tuvo que traer un nuevo concepto y paradigma de programación a Java), así como el uso de esos operadores intermedios como una pesadilla. Por lo tanto, el equipo original ideó el denominado diseño API fluido: una clase que contiene todos los métodos estáticos y de instancia y representa una fuente o etapa de procesamiento por sí mismo.

Debido a la naturaleza de los errores de primera clase, el soporte de concurrencia y la naturaleza funcional, se pueden encontrar todo tipo de fuentes y transformaciones con respecto al flujo reactivo. A medida que la biblioteca (y el concepto) evolucionaron desde los días de Rx.NET, se agregaron más y más operadores estándar, lo que por naturaleza aumentó el recuento de métodos. Esto lleva a dos quejas habituales:

  • ¿Por qué hay tantos métodos?
  • ¿Por qué no hay un método X que resuelva mi problema muy particular?

Escribir operadores reactivos es una tarea difícil que no mucha gente domina a lo largo de los años; La mayoría de los usuarios típicos de la biblioteca no pueden crear operadores por sí mismos (y a menudo no es realmente necesario que lo intenten). Esto significa que, de vez en cuando, agregamos más operadores al conjunto estándar. Por el contrario, hemos rechazado muchos más operadores debido a que son demasiado específicos o simplemente una conveniencia que no puede soportar su propio peso.

Diría que el diseño de RxJava creció orgánicamente y no siguiendo ciertos principios de diseño como SOLID. Se basa principalmente en el uso y la sensación de su fluida API.

Otras relaciones con Rx.NET

Me uní al desarrollo de RxJava a fines de 2013. Por lo que puedo decir, las primeras versiones iniciales de 0.x fueron en gran parte una reimplementación de recuadro negro donde Observablese reutilizaron los nombres y firmas de los operadores de Rx.NET , así como algunas decisiones arquitectónicas. Esto involucró aproximadamente el 20% de los operadores de Rx.NET. La principal dificultad en ese entonces era la resolución de las diferencias de lenguaje y plataforma entre C # y Java. Con gran esfuerzo, logramos implementar muchos operadores sin mirar el código fuente de Rx.NET y portamos los más complicados.

En este sentido, hasta RxJava 0.19, nuestro Observableera equivalente a Rx.NET IObservabley sus Observablemétodos de extensión complementarios . Sin embargo, apareció el llamado problema de contrapresión y RxJava 0.20 comenzó a divergir de Rx.NET a nivel de protocolo y arquitectura. Los operadores disponibles se ampliaron, muchos se volvieron conscientes de la contrapresión y presentamos nuevos tipos: Singley Completableen la era 1.x, que no tienen contrapartes en Rx.NET a partir de ahora.

La conciencia de contrapresión complica las cosas considerablemente y el 1.x lo Observablerecibió como una ocurrencia tardía. Juramos lealtad a la compatibilidad binaria, por lo que no fue posible cambiar el protocolo y la API.

Hubo otro problema con la arquitectura de Rx.NET: la cancelación síncrona no es posible porque para hacer eso, uno necesita Disposableque se devuelva antes de que el operador comience a ejecutar. Sin embargo, fuentes como Rangeestaban ansiosas y no regresan hasta que terminan. Este problema se puede resolver inyectando un Disposableen Observerlugar de devolver uno de subscribe().

RxJava 2.x fue rediseñado y reimplementado desde cero a lo largo de estas líneas. Tenemos un tipo independiente de contrapresión Flowableque ofrece el mismo conjunto de operadores que Observable. Observableno admite contrapresión y es algo equivalente a Rx.NET Observable. Internamente, todos los tipos reactivos inyectan su identificador de cancelación a sus consumidores, permitiendo que la cancelación síncrona funcione de manera eficiente.

akarnokd
fuente
10

Si bien admito que no estoy familiarizado con la biblioteca, eché un vistazo a la clase Flowable en cuestión y parece que está actuando como un tipo de centro. En otras palabras, es una clase destinada a validar entradas y distribuir llamadas en consecuencia en el proyecto.

Por lo tanto, esta clase no se consideraría realmente un objeto de Dios, ya que un objeto de Dios es aquel que intenta hacer todo. Esto hace muy poco en términos de lógica. En términos de responsabilidad única, se podría decir que el único trabajo de la clase es delegar el trabajo en toda la biblioteca.

Entonces, naturalmente, una clase así requeriría un método para cada tarea posible que requeriría de una Flowableclase en este contexto. Verá el mismo tipo de patrón con la biblioteca jQuery en javascript, donde la variable $tiene todas las funciones y variables necesarias para realizar llamadas en la biblioteca, aunque en el caso de jQuery, el código no solo se delega sino que también tiene una buena lógica se ejecuta dentro.

Creo que deberías tener cuidado de hacer una clase como esta, pero tiene su lugar siempre que el desarrollador recuerde que es solo un centro y, por lo tanto, no se convierte lentamente en un objeto divino.

Neil
fuente
2
Disculpas por eliminar la aceptación de su respuesta, que fue útil y esclarecedora, ¡pero la respuesta posterior de akarnokd fue de alguien en el equipo de RxJava!
skomisa
@skomisa ¡No podría esperar nada más si fuera mi propia pregunta! ¡Sin ofender! :)
Neil
7

El equivalente de .NET's RX Flowablees Observable . También tiene todos esos métodos, pero son estáticos y se pueden usar como métodos de extensión . El punto principal de RX es que la composición se escribe usando una interfaz fluida .

Pero para que Java tenga una interfaz fluida, necesita que esos métodos sean métodos de instancia, ya que los métodos estáticos no se compondrían bien y no tiene métodos de extensión para que los métodos estáticos sean componibles. Por lo tanto, en la práctica, todos esos métodos podrían convertirse en métodos estáticos, siempre y cuando estuvieras bien sin usar una sintaxis de interfaz fluida.

Eufórico
fuente
3
El concepto de interfaz fluido es, en mi humilde opinión, una solución para las limitaciones de idioma. Efectivamente, está construyendo un lenguaje usando una clase sobre Java. Si piensa en cuántas funciones tiene incluso un lenguaje de programación simple y no cuenta todas las variantes sobrecargadas, es bastante fácil ver cómo termina con tantos métodos. Las características funcionales de Java 8 pueden abordar muchos de los problemas que conducen a este tipo de diseño y los lenguajes contemporáneos como Kotlin se están moviendo para poder obtener el mismo tipo de beneficios sin la necesidad de encadenar métodos.
JimmyJames
Gracias a su publicación, profundicé más y parece que el Observable RX de .NET es ≈ al Observable RxJava, por lo que una premisa de mi pregunta era incorrecta. He actualizado el OP en consecuencia. Además, RxJava's Flowable ≈ (Observable + algunos métodos adicionales para paralelizar y manejar la contrapresión).
skomisa
@skomisa Java's Observable también tiene cientos de métodos. Entonces puedes comparar los dos. Pero la principal diferencia es que el Observable de .NET es estático y todos los métodos son estáticos. Mientras que Java no lo es. Y esa es una gran diferencia. Pero en la práctica, se comportan igual.
Eufórico el