Devo esporre IObservableT sulle mie interfacce?

6

Il mio collega e io abbiamo una disputa. Stiamo scrivendo un'applicazione .NET che elabora enormi quantità di dati. Riceve elementi di dati, raggruppa sottoinsiemi di essi in blocchi secondo alcuni criteri ed elabora tali blocchi.

Diciamo che abbiamo elementi di dati di tipo Foo che arrivano ad una fonte (dalla rete, per esempio) uno per uno. Desideriamo raccogliere sottoinsiemi di oggetti correlati di tipo Foo , costruire un oggetto di tipo Bar da ciascun sottoinsieme di questo tipo e oggetti di processo di tipo Bar .

Uno di noi ha suggerito il seguente disegno. Il suo tema principale è l'esposizione di oggetti IObservable direttamente dalle interfacce dei nostri componenti.

// ********* 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.
    }
}

L'altro ha suggerito un altro design il cui tema principale è l'utilizzo delle nostre interfacce editore / sottoscrittore e l'utilizzo di Rx all'interno delle implementazioni solo quando necessario.

//********** 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.
    }
}

Quale pensi sia meglio? Esporre IObservable e rendere i nostri componenti creare nuovi flussi di eventi dagli operatori Rx, o definire le nostre interfacce editore / sottoscrittore e utilizzare Rx internamente, se necessario?

Ecco alcuni aspetti da considerare sui design:

  • Nel primo design il consumatore delle nostre interfacce ha tutta la potenza di Rx a portata di mano e può eseguire qualsiasi operatore Rx. Uno di noi afferma che questo è un vantaggio e l'altro afferma che questo è uno svantaggio.

  • Il secondo design ci consente di utilizzare qualsiasi architettura di editore / sottoscrittore sotto il cofano. Il primo design ci lega a Rx.

  • Se desideriamo utilizzare la potenza di Rx, è necessario più lavoro nel secondo progetto perché è necessario tradurre l'implementazione del publisher / sottoscrittore personalizzato in Rx e ritorno. Richiede la scrittura di codice colla per ogni classe che desidera eseguire l'elaborazione di un evento.

posta Alex Shtof 09.07.2012 - 12:50
fonte

2 risposte

11

Penso che dire che IObservable<T> ti lega a Rx equivale a dire che IEnumerable<T> ti lega a LINQ. Sì, IObservable<T> è al centro di Rx e Rx ti consente di fare un sacco di cose con IObservable<T> .

Ma ciò non significa che devi utilizzare Rx per implementare le tue classi, anche se ereditano da IObservable<T> . Inoltre, non è necessario utilizzare Rx per lavorare con la classe, è possibile utilizzare direttamente Subscribe() . Credo che questo sia anche il motivo per cui IObservable<T> è incluso nel core del framework (è in mscorlib da .Net 4), ma Rx non lo è.

Quindi, a meno che IObservable<T> non soddisfi le tue esigenze, dovresti usarlo. Tra i lati positivi, avrai la possibilità di usare Rx. Sul lato negativo, potrebbe essere necessario avvolgere l'implementazione per renderla simile a IObservable<T> , se si decide di modificare l'implementazione in seguito. Ma dovresti fare lo stesso se hai usato un'interfaccia personalizzata.

IObservable<T> è solo un'interfaccia, non ti lega a nulla. Ma ti dà la possibilità di utilizzare le librerie che già funzionano con esso, a differenza di un'interfaccia personalizzata.

    
risposta data 09.07.2012 - 13:41
fonte
3

Innanzitutto, vale la pena notare che IObservable<T> fa parte di mscorlib.dll e System namespace, e quindi esporlo sarebbe in qualche modo equivalente all'esporre IComparable<T> o IDisposable . Che equivale a scegliere .NET come piattaforma, che sembra aver già fatto.

Ora, invece di suggerire una risposta, voglio suggerire una domanda diversa, e quindi una mentalità diversa, e spero (e mi fidi) che gestirai da lì.

In pratica stai chiedendo: Vogliamo promuovere l'uso sparso degli operatori Rx in tutto il nostro sistema? . Ora ovviamente non è molto invitante, visto che probabilmente concettualmente tratta Rx come una libreria di terze parti.

In entrambi i casi, la risposta non risiede nei disegni di base che voi due avete proposto, ma negli utenti di quei progetti. Raccomando di rompere il tuo design fino ai livelli di astrazione e assicurandoti che l'uso degli operatori Rx sia compreso in un solo livello. Quando parlo di livelli di astrazione, intendo qualcosa di simile al modello OSI , solo nello stesso codice dell'applicazione.

La cosa più importante, nel mio libro, è non prendere il punto di vista del design di "Creiamo qualcosa che verrà usato e sparso in tutto il sistema, e quindi dobbiamo assicurarci di farlo solo una volta e giusto, per tutti gli anni a venire ". Sono più un "Facciamo in modo che questo livello di astrazione produca l'API minima necessaria per gli altri livelli affinché al momento raggiungano i loro obiettivi" .

Per quanto riguarda la semplicità di entrambi i tuoi progetti, è davvero difficile giudicare poiché Foo e Bar non mi dicono molto sui casi d'uso , e quindi sui fattori di leggibilità (che sono, a proposito, diverso da un caso di utilizzo a un altro).

    
risposta data 09.07.2012 - 18:27
fonte

Leggi altre domande sui tag