私の同僚と私は論争をしています。大量のデータを処理する.NETアプリケーションを作成しています。データ要素を受け取り、それらのサブセットをいくつかの基準に従ってブロックにグループ化し、それらのブロックを処理します。
Foo
あるソース(たとえば、ネットワークから)に1つずつ到着するタイプのデータ項目があるとします。タイプの関連オブジェクトのサブセットを収集し、そのような各サブセットからFoo
タイプのオブジェクトを構築し、タイプのオブジェクトを処理したいと考えています。Bar
Bar
私たちの一人が次のデザインを提案しました。その主なテーマは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番目の設計でより多くの作業が必要になります。イベント処理を行うすべてのクラスにグルーコードを記述する必要があります。