我应该在我的接口上公开IObservable <t>吗?</t>

时间:2012-07-09 11:05:28

标签: c# .net system.reactive

我和我的同事有争议。我们正在编写一个处理海量数据的.NET应用程序。它接收数据元素,根据某些标准将它们的子集分成块,并处理这些块。

假设我们有类型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并返回。它需要为希望进行某些事件处理的每个类编写粘合代码。

4 个答案:

答案 0 :(得分:17)

公开IObservable<T>不会以任何方式污染设计Rx。实际上,设计决策与暴露旧学校.NET事件或滚动自己的pub / sub机制之间的待决决定完全相同。唯一的区别是IObservable<T>是更新的概念。

需要证明吗?看看F#,它也是一种.NET语言,但比C#更年轻。在F#中,每个事件都来自IObservable<T>。老实说,我认为抽象出一个完全合适的.NET发布/订阅机制 - 即IObservable<T>没有任何意义 - 远离你自己开发的pub / sub抽象。只需公开IObservable<T>

滚动自己的pub / sub抽象感觉就像将Java模式应用于.NET代码一样。不同的是,在.NET中,Observer模式总是有很好的框架支持,而且根本不需要自己动手。

答案 1 :(得分:8)

首先,值得注意的是IObservable<T>mscorlib.dllSystem命名空间的一部分,因此公开它会在某种程度上等同于公开IComparable<T>或{ {1}}。这相当于选择.NET作为您的平台,您似乎已经这样做了。

现在,我想提出一个不同的问题,而不是建议一个答案,而我希望(并且相信)你会从那里管理。

您基本上要问:我们是否希望在整个系统中促进Rx运营商的分散使用?。现在很明显,这并不是很吸引人,因为您可能在概念上将Rx视为第三方库。

无论哪种方式,答案都不在于你们两个提出的基础设计,而在于那些设计的用户。我建议将您的设计分解为抽象级别,并确保Rx运算符的使用仅限于一个级别。当我谈论抽象级别时,我指的是与OSI Model类似的东西,只是在同一个应用程序的代码中。

在我的书中,最重要的是不要采用的设计立场“让我们创造一些将在整个系统中使用和分散的东西,所以我们需要确保我们只做一次而且恰到好处,未来几年“。我更像是一个“让我们让这个抽象层产生其他层所需的最小API,以便当前实现他们的目标”

关于这两种设计的简单性,实际上很难判断,因为IDisposableFoo并没有告诉我很多关于用例的信息,因此可读性因素(顺便说一下,这个用例与另一个用例不同)。

答案 2 :(得分:2)

  

在第一个设计中,我们接口的消费者在他/她的指尖拥有Rx的全部功能,并且可以执行任何Rx操作符。我们中的一个人声称这是一个优势而另一个人声称这是一个缺点。

我同意Rx作为优势的可用性。列出一些原因,为什么它是一个缺点可以帮助确定如何解决它们。我看到的一些优点是:

  • 由于Yam和Christoph都反对,IObservable / IObserver从.NET 4.0开始就在mscorlib中,因此它(希望)将成为每个人都会立即理解的标准概念,如事件或IEnumerable。
  • Rx的运营商。一旦您需要编写,过滤或以其他方式操纵可能的多个流,这些变得非常有用。您可能会发现自己使用自己的界面以某种形式重做这项工作。
  • Rx的合同。 Rx库强制执​​行明确定义的合同,尽可能多地执行该合同。即使您需要创建自己的运营商,Observable.Create也会执行合同(这就是为什么Rx团队不建议直接实施IObservable)。
  • Rx库有很好的方法可以确保您在需要时最终使用正确的线程。

我已经编写了我的操作员份额,其中库不包括我的案例。

  

第二种设计允许我们使用任何发布者/订阅者体系结构。第一个设计将我们与Rx联系在一起。

我没有看到公开 Rx的选择如何影响你如何实现架构下的架构,而不仅仅是使用你自己的接口。我认为除非绝对必要,否则你不应该发明新的pub / sub架构。

此外,Rx库可能具有简化“引擎盖下”部分的运算符。

  

如果我们希望使用Rx的强大功能,则需要在第二个设计中进行更多工作,因为我们需要将自定义发布者/订阅者实现转换为Rx并返回。它需要为希望进行某些事件处理的每个类编写粘合代码。

是和否。如果我看到第二个设计,我会想到的第一件事是:“这几乎就像IObservable一样;让我们编写一些扩展方法来转换接口。”胶水代码只写一次,随处可用。

胶水代码很简单,但如果你认为你会使用Rx,只需暴露IObservable并省去麻烦。

进一步考虑

基本上,您的备用设计与IObservable / IObserver有三种不同的方式。

  1. 无法取消订阅。这可能只是在复制到问题时的疏忽。如果没有,那么如果你走那条路线就强烈考虑加入。
  2. 没有定义的错误流向下游的路径(例如IObserver.OnError)。
  3. 无法指示流的完成(例如IObserver.OnCompleted)。仅当您的基础数据旨在具有终止点时,这才有意义。
  4. 您的备用设计还将回调作为操作返回,而不是将其作为接口上的方法,但我认为这种区别并不重要。

    Rx库鼓励采用功能性方法。您的FooSubsetsToBarConverter类更适合作为返回IObservable<Foo>的{​​{1}}的扩展方法。这样可以略微减少混乱(为什么在函数完成时创建一个具有一个属性的类),并且更适合Rx库其余部分的链式组合。您可以将相同的方法应用于备用接口,但如果没有操作员的帮助,可能会更加困难。

答案 3 :(得分:0)

另一种选择可能是:

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

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

这样只有期望IObservableFooSource的客户端才能看到特定于RX的方法,那些期望IFooSource或FooSource的方法都不会。