私はRxJava、 ReactiveX のJavaの実装(Rxとしても知られています)リアクティブ拡張)。本当に私を驚かせたのは RxJavaのFlowableクラス の巨大なサイズでした:460のメソッドがあります!
公平であるために:
多くのメソッドがオーバーロードされているため、メソッドの総数が大幅に増加します。
おそらくこのクラスは分割する必要がありますが、RxJavaに関する私の知識と理解は非常に限られています。 RxJavaを作成した人たちは確かに非常に賢く、おそらく、多くのメソッドでFlowableを作成することを選択するための有効な引数を提供できます。
一方:
RxJavaはJava MicrosoftのReactive Extensions の実装)であり、Flowableクラスもないため、これは、既存のクラスを盲目的に移植してJavaで実装する場合ではありません。
[更新:イタリックの前のポイントは実際には正しくありません:400を超えるメソッドを持つMicrosoftのObservableクラスは、RxJavaのObservableクラスの基礎として使用されました、FlowableはObservableに似ていますが、大量のデータのバックプレッシャーを処理します。したがって、RxJavaチームは、既存のクラスを移植するでした。この投稿は、RxJavaのFlowableクラスではなく、MicrosoftによるObservableクラスの元の設計に挑戦しているはずです。]
RxJavaはわずか3年以上前のものなので、これは良い( [〜#〜] solid [〜#〜] )クラスについての知識がないためにコードが誤って設計されている例ではありません設計原則(Javaの初期リリースの場合と同様)。
Flowableと同じ大きさのクラスの場合、その設計は本質的に間違っているように見えますが、おそらくそうではありません。このSEの質問への1つの回答 クラスメソッドの数の制限は何ですか? 回答は「必要な数のメソッドを持っている 」.
言語に関係なく、それらをサポートするために相当数のメソッドを合法的に必要とするクラスがいくつかあることは明らかです。これらのクラスは、小さなものに容易に分解されず、かなりの数の特性と属性を持っているためです。たとえば、文字列、色、スプレッドシートのセル、データベースの結果セット、HTTPリクエストなどです。クラスがそれらを表現するためのおそらく数十のメソッドを持つことは不合理に思えない。
しかし、Flowableは本当に460のメソッドを必要としますか、それとも非常に巨大であり、必ずしも悪いクラス設計の例です。
[明確にするために:この質問は、一般に、神のオブジェクトではなく、RxJavaのFlowableクラスに特に関連しています。]
JavaはC#と比較して言語機能が不足していること、および検出可能性の考慮事項により、ソース演算子と中間演算子を大きなクラスに入れました。
オリジナルのRx.NETはC#3.0で開発され、拡張メソッドと部分クラスという2つの重要な機能を備えています。前者を使用すると、他のタイプのインスタンスメソッドを定義でき、そのターゲットメソッドの一部であるように見えますが、部分クラスを使用すると、大きなクラスを複数のファイルに分割できます。
これらの機能はどちらもJavaには存在していなかったため、RxJavaを十分に便利に使用できるようにする方法を見つける必要がありました。
RxJavaには2種類の演算子があります。静的なファクトリメソッドによって表されるソースのようなものと、インスタンスメソッドによって表される中間のようなものです。前者はどのクラスにも存在する可能性があるため、複数のユーティリティクラスに分散させることができます。後者では、インスタンスを処理する必要があります。概念的には、これらはすべて、アップストリームを最初のパラメーターとする静的メソッドを介して表現できます。
ただし、実際には、複数のエントリクラスがあると、新しいユーザーによる機能の発見が不便になります(RxJavaはJavaに新しいコンセプトとプログラミングパラダイムを導入する必要がありました)。したがって、元のチームは、いわゆる流暢なAPI設計を考案しました。すべての静的メソッドとインスタンスメソッドを保持し、それ自体でソースまたは処理段階を表す1つのクラスです。
エラーの第一級の性質、並行性のサポート、および機能の性質により、リアクティブフローに関するあらゆる種類のソースと変換が考えられます。 Rx.NETの時代からライブラリー(および概念)が進化するにつれて、ますます多くの標準オペレーターが追加され、それによって本質的にメソッド数が増えました。これにより、2つの通常の不満が生じます。
リアクティブオペレーターを作成することは困難な作業です。ほとんどの一般的なライブラリユーザーは、自分で演算子を作成することはできません(多くの場合、実際に試す必要はありません)。これは、時々、標準セットにさらに演算子を追加することを意味します。対照的に、あまりにも具体的すぎたり、単に自分の重さを引き出すことができない便利さのため、私たちはより多くの演算子を拒否しました。
RxJavaのデザインは有機的に成長し、SOLIDなどの特定のデザイン原則に沿ったものではないと思います。それは主に流暢なAPIの使用法と感触によって駆動されます。
私は2013年後半にRxJava開発に参加しました。私の知る限り、初期の初期の0.xバージョンは主にブラックボックスの再実装で、Rx.NET Observable
演算子の名前と署名、およびアーキテクチャ上の決定はほとんど再利用されませんでした。これには、Rx.NETオペレーターの約20%が関与しました。当時の主な問題は、C#とJavaの言語とプラットフォームの違いを解決することでした。多大な努力により、Rx.NETのソースコードを確認せずに多くのオペレーターを実装し、より複雑なオペレーターを移植しました。
この意味で、RxJava 0.19までは、Observable
はRx.NETのIObservable
およびそのコンパニオンObservable
拡張メソッドと同等でした。ただし、いわゆるバックプレッシャーの問題が発生し、RxJava 0.20はプロトコルおよびアーキテクチャレベルでRx.NETから分岐し始めました。利用可能な演算子が拡張され、多くがバックプレッシャ対応になり、1.x時代にはSingle
とCompletable
を新たに導入しました。現在、Rx.NETには対応するものはありません。
バックプレッシャの認識は事態をかなり複雑にし、1.x Observable
は後付けとしてそれを受け取りました。バイナリ互換性への忠誠を誓ったので、プロトコルとAPIを変更することはほとんど不可能でした。
Rx.NETのアーキテクチャには別の問題がありました。同期キャンセルは不可能です。そのためには、オペレーターが実行を開始する前にDisposable
を返す必要があります。ただし、Range
などのソースは熱心で、終了するまで戻りません。この問題は、Disposable
をsubscribe()
から返す代わりにObserver
に注入することで解決できます。
RxJava 2.xは 再設計および再実装 で、これらの線に沿ってゼロから作成されました。 Flowable
と同じ演算子のセットを提供する別のバックプレッシャ対応タイプObservable
があります。 Observable
はバックプレッシャーをサポートしておらず、Rx.NETのObservable
といくらか同等です。内部的には、すべてのリアクティブタイプはキャンセルハンドルをコンシューマーに注入し、同期キャンセルが効率的に機能できるようにします。
ライブラリに精通していないことは認めますが、問題の Flowable class を調べたところ、一種のハブのように機能しているようです。つまり、プロジェクトで入力を検証し、それに応じて呼び出しを分散することを目的としたクラスです。
したがって、このオブジェクトはすべてを実行しようとするオブジェクトなので、このクラスは実際にはオブジェクトとは見なされません。これはロジックに関してはほとんど機能しません。単一の責任という点では、クラスの唯一の仕事は、図書館全体の仕事を委任していると言えます。
したがって、当然、このようなクラスには、このコンテキストでFlowable
クラスに必要なすべての可能なタスクのメソッドが必要です。 JavaScriptのjQueryライブラリで同じタイプのパターンが表示されます。ここで、変数$
には、ライブラリの呼び出しを実行するために必要なすべての関数と変数が含まれています。ただしjQueryの場合は、コードが単純に委譲されるだけでなく、適切なロジックが実行されます。
このようなクラスを作成する場合は注意が必要だと思いますが、開発者がそれが単なるハブであり、したがってゆっくりとgodオブジェクトに変換されないことを覚えている限り、その場所はあります。
Flowable
に相当する.NETのRXは Observable です。これらのメソッドもすべて含まれていますが、静的であり、 拡張メソッド として使用できます。 RXの主なポイントは、構成が fluent interface を使用して記述されていることです。
しかし、Javaが流暢なインターフェイスを持つためには、静的メソッドはうまく構成できず、静的メソッドを構成可能にする拡張メソッドがないため、これらのメソッドをインスタンスメソッドにする必要があります。実際には、これらのメソッドはすべて、流れるようなインターフェイス構文を使用しないで問題なければ、静的メソッドにすることができます。