2012-07-09 38 views
17

我和我的同事有爭議。我們正在編寫處理大量數據的.NET應用程序。它接收數據元素,根據某些標準將它們的子集分組成塊,並處理這些塊。我應該在我的接口公開的IObservable <T>?

比方說,我們有Foo抵達的一些源類型的數據項(從網絡,例如)一個接一個。我們希望收集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. 
    } 
} 

其他建議,它的主題是用我們自己的發佈/訂閱接口和僅在需要時使用的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運營商。我們中的一個人聲稱這是一個優勢,另一個人聲稱這是一個缺點。

  • 第二個設計允許我們使用任何發佈/訂閱架構引擎蓋下。第一個設計將我們與Rx聯繫在一起。

  • 如果我們希望使用Rx的功率,它需要在第二個設計中更多的工作,因爲我們需要翻譯的自定義發佈/訂閱實施的Rx和背部。它需要爲每個希望進行事件處理的類編寫膠水代碼。

+3

我喜歡你開始這個問題的方式。 「我和我的同事有爭議。」 +1。 – 2012-07-09 11:40:23

+3

爲什麼不公開所有IObservable東西作爲*擴展方法*來處理所有「膠水代碼」。保持所有IObs與您的對象模型分離,同時提供選項。 '公共IObservable AsObservable(這個IPublisher 發佈者)'或類似的東西 – Will 2012-07-09 12:24:04

+4

你做得很好,以平衡的方式提出問題。 – 2012-07-09 12:46:42

回答

14

暴露IObservable<T>不會污染以任何方式與Rx的設計。實際上,設計決策與暴露舊式.NET事件或滾動您自己的pub/sub機制之間的完全相同。唯一的區別是IObservable<T>是更新的概念。

需要證據嗎?看看F#,它也是一種.NET語言,但比C#年輕。在F#中,每個事件都來自IObservable<T>。說實話,在抽象出完全合適的.NET pub/sub機制 - 即IObservable<T> - 中,我看不出有什麼意義 - 用你自己發佈的pub/sub抽象。只需公開IObservable<T>

滾動自己的pub/sub抽象的感覺就像Java的應用模式,以.NET代碼給我。不同之處在於,在.NET中,觀察者模式總是有很好的框架支持,並且根本不需要自己推出。

0

另一種選擇可能是:意想不到的IObservableFooSource會看到具體的RX-方法,那些期待一個IFooSource或FooSource不會

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

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

這樣,只有客戶。

8

首先,這是值得注意的是IObservable<T>mscorlib.dll部分和System命名空間,從而暴露那就有點相當於暴露IComparable<T>IDisposable。這相當於選擇.NET作爲您的平臺,您似乎已經完成了這一工作。

現在,而不是暗示的答案,我想提出一個不同的問題,再一個不同的心態,我希望(和信任),你會從那裏管理。

你基本上問:我們難道要促進所有在我們的系統中使用分散經營的Rx的?。顯然,這不是很吸引人,因爲你可能在概念上將Rx視爲第三方庫。

無論哪種方式,答案不在於你倆提出的基礎設計,但在這些設計的用戶。我建議將您的設計分解爲抽象層次,並確保Rx運算符的使用僅限於一個層次。當我談論抽象層次時,我的意思是類似於OSI Model,只在同一個應用程序的代碼中。

在我的書中,最重要的是不要採取的設計立場「讓我們創造一些將被使用並分散在整個系統中的東西,因此我們需要確保我們只做一次並且恰到好處,爲所有年來「。我更多的是的「讓我們把這個抽象層產生必要的其他層的最小API來目前實現自己的目標」

關於雙方你的設計的簡單,它實際上是很難判斷,因爲FooBar不要告訴我很多關於使用情況,因此可讀性因素(這是,順便說一句,從一個使用不同案件到另一個)。

2

在第一次設計了接口的消費者有Rx的整個電力在他/她的指尖,可以執行任何的Rx運營商。我們中的一個人聲稱這是一個優勢,另一個人聲稱這是一個缺點。

我將與Rx的可用性優勢同意。列舉一些缺點的原因可能有助於確定如何解決這些問題。一些優點我看到的是:

  • 山藥和Christoph都蹭着,的IObservable/IObserver在mscorlib程序作爲.NET 4.0,所以它會(希望)成爲一個標準的概念,每個人會立即明白,像事件或IEnumerable。
  • Rx的運營商。一旦您需要撰寫,過濾或以其他方式處理潛在的多個流,這些將變得非常有用。您可能會發現自己以某種形式使用自己的界面來重做這項工作。
  • Rx的合同。 Rx圖書館強制執行一個明確定義的合同,儘可能多地執行該合同。甚至當你需要使自己的運營商,Observable.Create將做強制執行合同的工作(這就是爲什麼實施IObservable直接不建議由RX隊)。
  • Rx庫有很好的方法來確保您在需要時能夠在最合適的線程上運行。

我寫了我的經營者份額,圖書館沒有涵蓋我的案件。

第二種設計允許我們使用任何發佈者/訂閱者架構。第一個設計將我們與Rx聯繫在一起。

我看不到的選擇如何暴露 RX具有多大,如果有的話,你如何實現油煙機不是使用自己的接口,將更多的下結構的影響。除非絕對必要,否則我會斷言你不應該發明新的pub/sub架構。

此外,Rx庫可能有簡化「引擎蓋下」部件的操作員。

如果我們希望使用Rx的力量,則需要在第二個設計中做更多工作,因爲我們需要將自定義發佈者/訂閱者實現轉換爲Rx並將其轉回。它需要爲每個希望進行事件處理的類編寫膠水代碼。

是,也不是。如果我看到第二個設計,我會首先考慮的是:「這幾乎就像IObservable;讓我們編寫一些擴展方法來轉換接口。」膠水代碼只寫一次,隨處使用。

膠水代碼非常簡單,但如果您認爲您會使用Rx,只需揭露IObservable並省去麻煩。

進一步的考慮

基本上,您的替代設計的不同之處從的IObservable/IObserver 3個鍵槽。

  1. 無法取消訂閱。在複製問題時,這可能只是一個疏忽。如果不是這樣,那麼如果你走這條路線,那麼強烈考慮添加。
  2. 沒有定義的錯誤向下遊流動的路徑(例如IObserver.OnError)。
  3. 無法指示流的完成(例如IObserver.OnCompleted)。這隻有在您的基礎數據有意終止時才適用。

您的替代設計也將回調作爲操作返回,而不是將其作爲接口上的方法,但我認爲區別並不重要。

Rx庫鼓勵使用功能性方法。您的FooSubsetsToBarConverter類將更適合作爲返回IObservable<Bar>IObservable<Foo>的擴展方法。這可以稍微減少混亂(爲什麼在一個函數可以正常工作時用一個屬性創建一個類),並更好地適應Rx庫其餘部分的鏈式組合。您可以將相同的方法應用於備用接口,但如果沒有操作員的幫助,則可能會更困難。

+1

+1好答案;合同至關重要。如果我們留在Rx內部,我們可以免費獲得一些保證,包括與其他LINQ monad(如IEnumerable/IQueryable/IQbservable)的免費副作用的保證。除非他們基本上重新發明了LINQ/Rx(monad),否則他們不能在設計中擁有這些保證。 – 2012-07-10 11:47:57

相關問題