20

私の同僚と私は論争をしています。大量のデータを処理する.NETアプリケーションを作成しています。データ要素を受け取り、それらのサブセットをいくつかの基準に従ってブロックにグループ化し、それらのブロックを処理します。

Fooあるソース(たとえば、ネットワークから)に1つずつ到着するタイプのデータ項目があるとします。タイプの関連オブジェクトのサブセットを収集し、そのような各サブセットからFooタイプのオブジェクトを構築し、タイプのオブジェクトを処理したいと考えています。BarBar

私たちの一人が次のデザインを提案しました。その主なテーマはIObservable<T>、コンポーネントのインターフェイスから直接オブジェクトを公開することです。

// ********* Interfaces **********
interface IFooSource
{
    // this is the event-stream of objects of type Foo
    IObservable<Foo> FooArrivals { get; }
}

interface IBarSource
{
    // this is the event-stream of objects of type Bar
    IObservable<Bar> BarArrivals { get; }
}

/ ********* Implementations *********
class FooSource : IFooSource
{
    // Here we put logic that receives Foo objects from the network and publishes them to the FooArrivals event stream.
}

class FooSubsetsToBarConverter : IBarSource
{
    IFooSource fooSource;

    IObservable<Bar> BarArrivals
    {
        get
        {
            // Do some fancy Rx operators on fooSource.FooArrivals, like Buffer, Window, Join and others and return IObservable<Bar>
        }
    }
}

// this class will subscribe to the bar source and do processing
class BarsProcessor
{
    BarsProcessor(IBarSource barSource);
    void Subscribe(); 
}

// ******************* Main ************************
class Program
{
    public static void Main(string[] args)
    {
        var fooSource = FooSourceFactory.Create();
        var barsProcessor = BarsProcessorFactory.Create(fooSource) // this will create FooSubsetToBarConverter and BarsProcessor

        barsProcessor.Subscribe();
        fooSource.Run(); // this enters a loop of listening for Foo objects from the network and notifying about their arrival.
    }
}

もう1つは、メインテーマが独自のパブリッシャー/サブスクライバーインターフェイスを使用し、必要な場合にのみ実装内でRxを使用するという別の設計を提案しました。

//********** interfaces *********

interface IPublisher<T>
{
    void Subscribe(ISubscriber<T> subscriber);
}

interface ISubscriber<T>
{
    Action<T> Callback { get; }
}


//********** implementations *********

class FooSource : IPublisher<Foo>
{
    public void Subscribe(ISubscriber<Foo> subscriber) { /* ...  */ }

    // here we put logic that receives Foo objects from some source (the network?) publishes them to the registered subscribers
}

class FooSubsetsToBarConverter  : ISubscriber<Foo>, IPublisher<Bar>
{
    void Callback(Foo foo)
    {
        // here we put logic that aggregates Foo objects and publishes Bars when we have received a subset of Foos that match our criteria
        // maybe we use Rx here internally.
    }

    public void Subscribe(ISubscriber<Bar> subscriber) { /* ...  */ }
}

class BarsProcessor : ISubscriber<Bar>
{
    void Callback(Bar bar)
    {
        // here we put code that processes Bar objects
    }
}

//********** program *********
class Program
{
    public static void Main(string[] args)
    {
        var fooSource = fooSourceFactory.Create();
        var barsProcessor = barsProcessorFactory.Create(fooSource) // this will create BarsProcessor and perform all the necessary subscriptions

        fooSource.Run();  // this enters a loop of listening for Foo objects from the network and notifying about their arrival.
    }
}

どちらが良いと思いますか?コンポーネントを公開IObservable<T>して作成すると、Rxオペレーターから新しいイベントストリームが作成されますか、それとも独自のパブリッシャー/サブスクライバーインターフェイスを定義し、必要に応じてRxを内部で使用しますか?

デザインについて考慮すべき点がいくつかあります。

  • 最初の設計では、インターフェイスの利用者は指先でRxの全機能を利用でき、任意のRxオペレーターを実行できます。私たちの1人はこれが利点であると主張し、もう1人はこれが欠点であると主張します。

  • 2番目の設計では、内部で任意のパブリッシャー/サブスクライバーアーキテクチャを使用できます。最初のデザインは私たちをRxに結び付けます。

  • Rxの機能を使用する場合は、カスタムのパブリッシャー/サブスクライバーの実装をRxに変換して戻す必要があるため、2番目の設計でより多くの作業が必要になります。イベント処理を行うすべてのクラスにグルーコードを記述する必要があります。

4

4 に答える 4

16

露出しても、Rxでデザインが汚染IObservable<T>されることはありません。実際、設計上の決定は、古い学校の.NETイベントを公開するか、独自のpub/subメカニズムをローリングするかを保留するのとまったく同じです。唯一の違いは、それが新しいコンセプトであるということです。IObservable<T>

証拠が必要ですか?.NET言語でもあるがC#よりも若いF#を見てください。F#では、すべてのイベントはから派生しIObservable<T>ます。正直なところ、完全に適切な.NET pub / subメカニズムを抽象化することに意味はありません。つまりIObservable<T>、自家製のpub/sub抽象化を使用する必要はありません。公開するだけIObservable<T>です。

独自のpub/sub抽象化をロールすることは、Javaパターンを.NETコードに適用するようなものです。違いは、.NETでは常にオブザーバーパターンの優れたフレームワークサポートがあり、独自のフレームワークを作成する必要がないことです。

于 2012-07-09T21:48:28.177 に答える
8

まず、それが名前空間IObservable<T>の一部であることに注意する価値がmscorlib.dllあります。したがって、それを公開することは、またはSystemを公開することとある程度同等です。これは、プラットフォームとして.NETを選択するのと同じですが、これはすでに行っているようです。IComparable<T>IDisposable

さて、答えを提案するのではなく、別の質問を提案し、次に別の考え方を提案したいと思います。そこから管理してくれることを願っています(そして信頼しています)。

あなたは基本的に質問しています:私たちはシステム全体でRxオペレーターの分散使用を促進したいですか?。おそらく概念的にRxをサードパーティのライブラリとして扱っているので、明らかにそれはあまり魅力的ではありません。

いずれにせよ、答えは2人が提案した基本的なデザインではなく、それらのデザインのユーザーにあります。デザインを抽象化レベルに分解し、Rx演算子の使用が1つのレベルに限定されていることを確認することをお勧めします。抽象化レベルについて話すとき、私は同じアプリケーションのコードでのみ、 OSIモデルに似たものを意味します。

私の本で最も重要なことは、「システム全体に使用され、散らばるようなものを作成しましょう。そのため、一度だけ正しく実行するようにする必要があります。これからのすべての年」。私は「この抽象化レイヤーで、他のレイヤーが現在目標を達成するために必要な最小限のAPIを生成するようにしましょう」というよりも重要です。

両方のデザインの単純さについては、実際には判断が難しく、ユースケース、つまり読みやすさの要因(ユースケースごとに異なります)についてはあまり教えてくれませんFooBar

于 2012-07-09T16:27:05.870 に答える
2

最初の設計では、インターフェイスの利用者は指先でRxの全機能を利用でき、任意のRxオペレーターを実行できます。私たちの1人はこれが利点であると主張し、もう1人はこれが欠点であると主張します。

私は利点としてRxの可用性に同意します。それが欠点であるいくつかの理由をリストすることは、それらに対処する方法を決定するのに役立つ可能性があります。私が見るいくつかの利点は次のとおりです。

  • YamとChristophの両方が反対しているように、IObservable /IObserverは.NET4.0の時点でmscorlibに含まれているため、イベントやIEnumerableのように、誰もがすぐに理解できる標準の概念になることを願っています。
  • Rxの演算子。潜在的に複数のストリームを作成、フィルタリング、またはその他の方法で操作する必要がある場合、これらは非常に役立ちます。おそらく、独自のインターフェイスを使用して、この作業を何らかの形でやり直すことに気付くでしょう。
  • Rxの契約。Rxライブラリは、明確に定義されたコントラクトを適用し、そのコントラクトの実行を可能な限り実行します。独自のオペレーターを作成する必要がある場合でもObservable.Create、契約を実施するための作業を行います(そのためIObservable、Rxチームは直接実装することをお勧めしません)。
  • Rxライブラリには、必要なときに正しいスレッドにたどり着くための優れた方法があります。

ライブラリが私のケースをカバーしていない場合、私は演算子のシェアを書きました。

2番目の設計では、内部で任意のパブリッシャー/サブスクライバーアーキテクチャを使用できます。最初のデザインは私たちをRxに結び付けます。

Rxを公開するという選択が、独自のインターフェイスを使用するよりも、内部でアーキテクチャを実装する方法にどのように影響するかはわかりません。どうしても必要な場合を除いて、新しいpub/subアーキテクチャを発明するべきではないと断言します。

さらに、Rxライブラリには、「内部」の部分を単純化する演算子が含まれている場合があります。

Rxの機能を使用する場合は、カスタムのパブリッシャー/サブスクライバーの実装をRxに変換して戻す必要があるため、2番目の設計でより多くの作業が必要になります。イベント処理を行うすべてのクラスにグルーコードを記述する必要があります。

はいといいえ。2番目のデザインを見た場合に最初に思うのは、「これはIObservableにほぼ似ています。インターフェイスを変換するための、いくつかの拡張メソッドを作成しましょう」です。グルーコードは一度書かれ、どこでも使用されます。

グルーコードは簡単ですが、Rxを使用すると思われる場合は、IObservableを公開するだけで、面倒な作業を省くことができます。

さらなる考慮事項

基本的に、代替設計はIObservable/IObserverとは3つの主要な点で異なります。

  1. 登録を解除する方法はありません。これは、質問にコピーするときの見落としかもしれません。そうでない場合は、そのルートに行く場合は追加することを強く検討する必要があります。
  2. エラーがダウンストリームに流れるための定義されたパスはありません(例IObserver.OnError)。
  3. ストリームの完了を示す方法はありません(例IObserver.OnCompleted)。これは、基になるデータにターミネーションポイントを設定することを目的としている場合にのみ関係します。

別のデザインでも、コールバックをインターフェイスのメソッドとしてではなくアクションとして返しますが、区別は重要ではないと思います。

Rxライブラリは、機能的なアプローチを推奨します。あなたのクラスは、それを返すFooSubsetsToBarConverter拡張メソッドとしてより適しています。これにより、煩雑さがわずかに軽減され(関数が正常に機能するのに、なぜ1つのプロパティを持つクラスを作成するのか)、残りのRxライブラリのチェーンスタイルの構成によりよく適合します。同じアプローチを代替インターフェースに適用することもできますが、支援するオペレーターがいなければ、それはより困難になる可能性があります。IObservable<Foo>IObservable<Bar>

于 2012-07-10T04:35:14.273 に答える
0

別の選択肢は次のとおりです。

interface IObservableFooSource : IFooSource
{
    IObservable<Foo> FooArrivals
    {
        get;
    }
}

class FooSource : IObservableFooSource 
{
    // Implement the interface explicitly
    IObservable<Foo> IObservableFooSource.FooArrivals
    {
        get
        {
        }
    }
}

このように、IObservableFooSourceを期待するクライアントのみがRX固有のメソッドを認識し、IFooSourceまたはFooSourceを期待するクライアントは認識しません。

于 2012-07-09T12:44:18.730 に答える