2012-07-09 54 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

相关问题