Mi colega y yo tenemos una disputa. Estamos escribiendo una aplicación .NET que procesa grandes cantidades de datos. Recibe elementos de datos, los agrupa en subconjuntos en bloques de acuerdo con algún criterio y procesa esos bloques.¿Debo exponer IObservable <T> en mis interfaces?
Digamos que tenemos elementos de datos de tipo Foo
llegando alguna fuente (de la red, por ejemplo) uno por uno. Deseamos juntar subconjuntos de objetos relacionados de tipo Foo
, construir un objeto de tipo Bar
desde cada subconjunto y procesar objetos del tipo Bar
.
Uno de nosotros sugirió el siguiente diseño. Su tema principal es exponer objetos IObservable<T>
directamente desde las interfaces de nuestros componentes.
// ********* 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.
}
}
El otro sugirió otro diseño que su tema principal está utilizando nuestras propias interfaces editor/suscriptor y el uso de Rx dentro de las implementaciones sólo cuando sea necesario.
//********** 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.
}
}
¿Cuál crees que es mejor? ¿Exponiendo IObservable<T>
y haciendo que nuestros componentes creen nuevas secuencias de eventos de los operadores de Rx, o que defina nuestras propias interfaces de editor/suscriptor y que usen Rx internamente si es necesario?
Aquí están algunas cosas a considerar sobre los diseños:
En el primer diseño del consumidor de nuestras interfaces tiene todo el poder de Rx a su/sus dedos y puede realizar cualquier operador Rx. Uno de nosotros afirma que esto es una ventaja y el otro afirma que esto es un inconveniente.
El segundo diseño nos permite utilizar cualquier arquitectura de editor/suscriptor bajo el capó. El primer diseño nos une a Rx.
Si deseamos usar la potencia de Rx, se requiere más trabajo en el segundo diseño porque necesitamos traducir la implementación personalizada de editor/suscriptor a Rx y viceversa. Requiere escribir código de pegamento para cada clase que desee hacer algún procesamiento de eventos.
Me gusta la forma en que comienzas esta pregunta. "Mi colega y yo tenemos disputa". +1. –
¿Por qué no exponer todas las cosas IObservable como * métodos de extensión * que se encargan de todo ese "código de pegamento".Mantiene todas las cosas IObs separadas de sus modelos de objetos, al tiempo que ofrece la opción. 'public IObservable AsObservable (este editor IPublisher )' o algo similar –
Will
Hiciste un buen trabajo al hacer la pregunta de forma equilibrada. –