La classe RxJava Flowable può legittimamente avere 460 metodi?


14

Ho appena iniziato con RxJava , l'implementazione Java di ReactiveX (noto anche come Rx e Reactive Extensions ). Qualcosa che mi ha colpito è stata la dimensione di massa di di RxJava Flowable classe : ha 460 metodi!

Ad essere onesti:

  • Esistono molti metodi sovraccaricati, che aumentano significativamente il numero totale di metodi.

  • Forse questa classe dovrebbe essere suddivisa, ma la mia conoscenza e comprensione di RxJava è molto limitata. Le persone che hanno creato RxJava sono sicuramente molto intelligenti e possono presumibilmente offrire argomenti validi per scegliere di creare Flowable con così tanti metodi.

D'altro canto:

  • RxJava è l'implementazione Java delle estensioni reattive di Microsoft e che non ha nemmeno una classe Flowable , quindi questo non è il caso di portare ciecamente una classe esistente e implementarla in Java.

  • [ Aggiornamento: il punto precedente in corsivo non è corretto: la classe Observable di Microsoft , che ha oltre 400 metodi, è stata utilizzata come base per la classe Observable di RxJava e Flowable è simile a Observable ma gestisce la contropressione per grandi volumi di dati. Quindi il team RxJava stava eseguendo il porting di una classe esistente. Questo post avrebbe dovuto essere sfidando il disegno originale del Observable di classe da parte di Microsoft, piuttosto che di RxJava Fluido di classe.]

  • RxJava ha solo poco più di 3 anni, quindi questo non è un esempio di codice mal progettato a causa della mancanza di conoscenza dei principi di progettazione di classe ( SOLID ) (come nel caso delle prime versioni di Java).

Per una classe grande come Flowable il suo design sembra intrinsecamente sbagliato, ma forse no; una risposta a questa domanda SE Qual è il limite al numero di metodi di una classe? ha suggerito che la risposta è " Avere tutti i metodi di cui hai bisogno ".

Chiaramente ci sono alcune classi che hanno legittimamente bisogno di un discreto numero di metodi per supportarli indipendentemente dal linguaggio, perché non si scompongono prontamente in qualcosa di più piccolo e hanno un discreto numero di caratteristiche e attributi. Ad esempio: stringhe, colori, celle di fogli di calcolo, set di risultati del database e richieste HTTP. Avere forse qualche dozzina di metodi affinché le classi rappresentino quelle cose non sembra irragionevole.

Ma Flowable ha davvero bisogno di 460 metodi o è così grande che è necessariamente un esempio di design di classe scadente?

[Per essere chiari: questa domanda riguarda specificamente di RxJava Flowable classe piuttosto che oggetti Dio in generale.]



1
@gnat Beh sicuramente correlato, ma non è un duplicato. Tale questione era generica, e la mia domanda riguarda specificamente di RxJava Fluido di classe.
skomisa,

@skomisa Quindi correggi il titolo per adattarlo alla tua domanda.
Euforico

@ Punto euforico preso.
skomisa,

1
Questa domanda è molto interessante e fondata. Tuttavia, suggerirei di riformularlo leggermente al fine di adottare uno stile meno soggettivo (probabilmente a causa dello shock iniziale ;-))
Christophe

Risposte:


14

TL; DL

La mancanza di funzionalità linguistiche di Java rispetto a C # e le considerazioni sulla rilevabilità ci hanno portato a mettere gli operatori di origine e intermedi in grandi classi.

Design

L'originale Rx.NET è stato sviluppato in C # 3.0 che ha due caratteristiche cruciali: metodi di estensione e classi parziali. Il primo consente di definire metodi di istanza su altri tipi che sembrano quindi far parte di quel tipo di destinazione, mentre le classi parziali consentono di dividere classi di grandi dimensioni in più file.

Nessuna di queste funzionalità era o è presente in Java, quindi, abbiamo dovuto trovare un modo per rendere RxJava utilizzabile abbastanza comodamente.

Esistono due tipi di operatori in RxJava: il tipo di sorgente rappresentato da metodi statici di fabbrica e il tipo di intermedio rappresentato da metodi di istanza. I primi potevano vivere in qualsiasi classe e quindi avrebbero potuto essere distribuiti su più classi di utilità. Quest'ultimo richiede un'istanza su cui lavorare. In teoria, tutto ciò potrebbe essere espresso tramite metodi statici con l'upstream come primo parametro.

In pratica, tuttavia, avere più classi di accesso rende scomoda la scoperta di nuove funzionalità da parte di nuovi utenti (ricordate, RxJava ha dovuto introdurre un nuovo concetto e paradigma di programmazione in Java) e l'utilizzo di quegli operatori intermedi in qualche modo un incubo. Pertanto, il team originale ha escogitato il cosiddetto design API fluente: una classe che contiene tutti i metodi statici e di istanza e rappresenta una fase di origine o elaborazione da sola.

A causa della natura di prima classe degli errori, del supporto della concorrenza e della natura funzionale, si possono trovare tutti i tipi di fonti e trasformazioni riguardanti il ​​flusso reattivo. Man mano che la libreria (e il concetto) si sono evoluti dai tempi di Rx.NET, sono stati aggiunti sempre più operatori standard, che per natura hanno aumentato il conteggio dei metodi. Ciò ha portato a due solite lamentele:

  • Perché ci sono così tanti metodi?
  • Perché non esiste un metodo X che risolva il mio problema molto particolare?

Scrivere operatori reattivi è un compito difficile che non molte persone hanno imparato negli anni; gli utenti della biblioteca più tipici non possono creare operatori da soli (e spesso non è proprio necessario che provino). Ciò significa che, di volta in volta, aggiungiamo ulteriori operatori all'insieme standard. Al contrario, abbiamo rifiutato molti più operatori per essere troppo specifici o semplicemente una comodità che non è in grado di sopportare il proprio peso.

Direi che il design di RxJava è cresciuto in modo organico e non proprio secondo certi principi di design come SOLID. È principalmente guidato dall'uso e dalla sensazione della sua API fluente.

Altre relazioni con Rx.NET

Ho aderito allo sviluppo di RxJava alla fine del 2013. Per quanto ne so, le prime versioni iniziali 0.x erano in gran parte una reimplementazione black-box in cui Observablevenivano riutilizzati i nomi e le firme degli operatori Rx.NET e alcune decisioni sull'architettura. Ciò ha coinvolto circa il 20% degli operatori Rx.NET. La principale difficoltà di allora era la risoluzione delle differenze di linguaggio e piattaforma tra C # e Java. Con grande sforzo, siamo riusciti a implementare molti operatori senza guardare il codice sorgente di Rx.NET e abbiamo portato quelli più complicati.

In questo senso, fino a RxJava 0.19, il nostro Observableera equivalente a Rx.NET IObservablee ai suoi Observablemetodi di estensione associati . Tuttavia, è apparso il cosiddetto problema di contropressione e RxJava 0.20 ha iniziato a divergere da Rx.NET a livello di protocollo e di architettura. Gli operatori disponibili sono stati ampliati, molti sono diventati consapevoli della contropressione e abbiamo introdotto nuovi tipi: Singlee Completablenell'era 1.x, che al momento non hanno controparti in Rx.NET.

La consapevolezza della contropressione complica notevolmente le cose e l'1x l'ha Observablericevuto come ripensamento. Abbiamo giurato fedeltà alla compatibilità binaria, quindi non è stato possibile modificare il protocollo e l'API.

C'era un altro problema con l'architettura di Rx.NET: la cancellazione sincrona non è possibile perché per fare ciò, è necessario Disposableche sia restituito prima che l'operatore inizi l'esecuzione. Tuttavia, fonti come Rangeerano impazienti e non ritornano fino al termine. Questo problema può essere risolto iniettando una Disposablenella Observerinvece di restituire uno da subscribe().

RxJava 2.x è stato riprogettato e reimplementato da zero lungo queste linee. Abbiamo un tipo separato, sensibile alla contropressione, Flowableche offre lo stesso insieme di operatori di Observable. Observablenon supporta la contropressione ed è in qualche modo equivalente a quello di Rx.NET Observable. Internamente, tutti i tipi reattivi iniettano il loro handle di cancellazione ai propri consumatori, consentendo alla cancellazione sincrona di funzionare in modo efficiente.


10

Mentre ammetto che non ho familiarità con la biblioteca, ho dato un'occhiata alla classe Flowable in questione e sembra che si stia comportando come un hub. In altre parole, è una classe pensata per convalidare l'input e distribuire le chiamate di conseguenza nel progetto.

Questa classe quindi non sarebbe davvero considerata un oggetto di Dio in quanto un oggetto di Dio è uno che tenta di fare tutto. Questo fa molto poco in termini di logica. In termini di responsabilità singola, si può dire che l'unico lavoro della classe è delegare il lavoro in tutta la biblioteca.

Quindi, naturalmente, una classe del genere richiederebbe un metodo per ogni possibile attività richiesta da una Flowableclasse in questo contesto. Vedi lo stesso tipo di pattern con la libreria jQuery in javascript, dove la variabile $ha tutte le funzioni e le variabili necessarie per eseguire le chiamate sulla libreria, anche se nel caso di jQuery, il codice non è semplicemente delegato ma anche un po 'di logica viene eseguito all'interno.

Penso che dovresti stare attento a creare una classe come questa, ma ha il suo posto fintanto che lo sviluppatore ricorda che è solo un hub, e quindi non si converte lentamente in un oggetto divino.


2
Ci scusiamo per aver rimosso l'accettazione della tua risposta, che è stata utile e illuminante, ma la successiva risposta di Akarnokd è stata da parte di qualcuno del team RxJava!
skomisa,

@skomisa Non potrei sperare in niente di più se fosse una mia domanda! Senza offesa! :)
Neil

7

L'equivalente di .NET RX Flowableè osservabile . Ha anche tutti questi metodi, ma sono statici e utilizzabili come metodi di estensione . Il punto principale di RX è che la composizione è scritta usando un'interfaccia fluida .

Ma affinché Java abbia un'interfaccia fluida, ha bisogno che quei metodi siano metodi di istanza, poiché i metodi statici non si comporterebbero bene e non ha metodi di estensione per rendere i metodi statici componibili. Quindi, in pratica, tutti questi metodi potrebbero essere resi statici, purché tu stia bene a non usare la sintassi dell'interfaccia fluente.


3
Il concetto di interfaccia fluida è, IMHO, una soluzione alternativa per le limitazioni linguistiche. In effetti, stai costruendo un linguaggio usando una classe su Java. Se pensi a quante funzionalità ha anche un semplice linguaggio di programmazione e non conti tutte le varianti sovraccariche, diventa abbastanza facile vedere come si finisce con questi molti metodi. Le caratteristiche funzionali di Java 8 possono affrontare molti dei problemi che portano a questo tipo di design e linguaggi contemporanei come Kotlin si stanno muovendo per essere in grado di ottenere lo stesso tipo di benefici senza la necessità di concatenare il metodo.
JimmyJames,

Grazie al tuo post ho scavato più a fondo e sembra che RX's Observable di .NET sia ≈ rispetto a RxJava's Observable, quindi una premessa della mia domanda era errata. Ho aggiornato il PO di conseguenza. Inoltre, RxJava's Flowable ≈ (Observable + alcuni metodi extra per parallelizzare e gestire la contropressione).
skomisa,

Anche @skomisa Java's Observable ha centinaia di metodi. Quindi puoi confrontare i due. Ma la differenza principale è che .NET Observable è statico e tutti i metodi sono statici. Mentre Java non lo è. E questa è un'enorme differenza. Ma in pratica si comportano allo stesso modo.
Euforico,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.