Mój kolega i ja mamy spór. Piszemy aplikację .NET, która przetwarza ogromne ilości danych. Odbiera elementy danych, grupuje je podzbiorami na bloki według pewnego kryterium i przetwarza te bloki.Czy powinienem wystawiać IObservable <T> na moich interfejsach?
Załóżmy, że elementy danych typu Foo
docierają do niektórych źródeł (np. Z sieci) jeden po drugim. Chcemy zebrać podzbiory powiązanych obiektów typu Foo
, skonstruować obiekt typu Bar
z każdego takiego podzbioru i obiektów procesu typu Bar
.
Jeden z nas zasugerował następujący projekt. Jego głównym tematem jest eksponowanie obiektów bezpośrednio z interfejsów naszych komponentów.
// ********* 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.
}
}
Druga sugeruje inną konstrukcję, że jego głównym tematem jest za pomocą naszych własnych interfejsów wydawca/subskrybent i korzystając Rx wewnątrz implementacji tylko w razie potrzeby.
//********** 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.
}
}
Który z nich jest lepszy? Wyświetlając IObservable<T>
i tworząc nasze komponenty, tworzysz nowe strumienie zdarzeń od operatorów Rx, lub definiujesz własne interfejsy wydawcy/subskrybenta i wewnętrznie używasz Rx w razie potrzeby?
Oto kilka rzeczy do rozważenia o wzory:
W pierwszym projekcie konsument naszych interfejsów ma całą moc Rx na jego/jej palców i może wykonywać żadnych operatorów RX. Jeden z nas twierdzi, że jest to zaleta, a drugi twierdzi, że jest to wadą.
Drugi projekt pozwala nam używać dowolnej architektury wydawcy/subskrybenta pod maską. Pierwszy projekt wiąże nas z Rx.
Jeśli chcemy korzystać z mocy Rx, wymaga to więcej pracy w drugim projekcie, ponieważ musimy przetłumaczyć niestandardową implementację wydawcy/subskrybenta na Rx iz powrotem. Wymaga napisania kodu kleju dla każdej klasy, która chce wykonać pewne przetwarzanie zdarzeń.
Podoba mi się sposób, w jaki rozpoczynasz to pytanie. "Ja i mój kolega mamy spór.". +1. –
Dlaczego nie narażać wszystkich rzeczy IObservable jako * metod rozszerzenia *, które dbają o cały ten "kod kleju".Zachowuje wszystkie elementy IObs oddzielnie od modeli obiektów, jednocześnie oferując opcję. 'public IObservable AsObservable (to IPublisher wydawca)' lub coś podobnego –
Will
Zrobiłeś dobrą robotę zadając pytanie w sposób zrównoważony. –