I wonder if its ok from an architectural standpoint to pass a subject into a component. What I actually want is to have the component expose an observable. However, I would like to control where this observable stream comes from so thats why Im asking if its fine to pass in a subject where the component can raise "events" on.
Ok, let's elaborate on this.
Say, we are designing a component that takes userinput, throttles the keystrokes and shows up a result list. The actual search is happening on another service component.
I would like to design the SearchWidget creator function like this:
//notice how I just avoided the word "constructor". However, conside this code as
//language agnostic. Could be RxJs or Rx .NET. It's Rx(ish)!
function SearchWidget(userInputStream, resultStream){
// do some funky Rx hotness!
}
A higher level component (say a controller/mediator) will actually hook up the streams.
Obviously, the resultStream needs the inputStream to get the work done.
In our example above, the resultStream will be a simply observable from the perspective of the Se开发者_StackOverflowarchWidget on which it can listen for the result list. However it will be implemented as Subject within the higher level component.
In contrast, the userInputStream will be a subject from the perspective of the SearchWidget but it will be instanced on the higher component level because we need it in advance to get the resultStream hooked up. Though, from the perspective of the higher level component, its a simple observable.
The higher order code may look like this:
//This code lives in a higher order component (say a controller/mediator)
var resultStream = new Rx.Subject();
var userInputStream = new Rx.Subject();
userInputStream
.Throttle(500)
.DistinctUntilChanged()
.Select(service.search) //service comes from somewhere.
.Switch()
.Subscribe(resultStream.OnNext,
resultStream.OnError,
resultStream.OnCompleted);
var searchWidget = new SearchWidget(userInputStream, resultStream.AsObservable());
In the implementation above, I use the userInputStream before the SearchWidget was initialized. Of course I could also implement it this way:
//implementation of the search widget
function SearchWidget(resultStream){
var userInputStream = new Rx.Subject();
// provide something like getUserInputStream()
// that will return unserInputStream.AsObservable()
// do some funky Rx hotness!
}
//This code lives in a higher order component (say a controller/mediator)
var resultStream = new Rx.Subject();
var searchWidget = new SearchWidget(resultStream);
//we need to initialize the searchWidget in advance to get the userInputStream
searchWidget
.getUserInputStream()
.Throttle(500)
.DistinctUntilChanged()
.Select(service.search) //service comes from somewhere.
.Switch()
.Subscribe(resultStream.OnNext,
resultStream.OnError,
resultStream.OnCompleted);
So, from the standpoint of encapsulation, the second implementation may look more robust. However, passing in the subject provides richer flexibility.
Since the concept of working with event streams is pretty modern I'm struggeling to find best practices for the bigger picture when designing applications with event streams.
For me it looks like it just needs a bit of a reshuffle and a bit of thought into what would be the clearest way to express what is going on (Rx can very easily be overused).
Looking at your example I see nothing wrong with exposing the user input as an IObservable (through your GetUserInputStream), and also nothing wrong in continuing to handle the stream in your higher level controllers/mediators, but to me there isn't a need to pass the resultStream Subject in. I can't see any reason you cant use normal methods on the SearchWidget to handle the results. Something like:
var searchWidget = new SearchWidget();
searchWidget
.GetUserInputStream()
.Throttle(500)
.DistinctUntilChanged()
.Select(service.search) //service comes from somewhere.
.Switch()
.Subscribe(result => searchWidget.HandleSearchResult(result),
ex => searchWidget.HandleSeachError(ex),
() => searchWidget.HandleSearchComplete());
It's a lot more explicit and you'll be able to express in a much clearer way, through well named methods, what the results will be on the SearchWidget.
If you did then want to handle the internals to these responses through Rx, there would be nothing wrong with instancing a subject internally and processing the responses from the method calls. Such as:
public class SearchWidget
{
private ISubject subject;
public SearchWidget()
{
this.subject = new Subject();
//do funky rx stuff on the subject here
}
public void HandleSearchResult(SearchResult result)
{
subject.OnNext(result);
}
public void HandleSearchError(Exception ex)
{
subject.OnError(ex);
}
public void HandleSearchComplete()
{
subject.OnCompleted();
}
public IObservable<MouseEvent> GetUserInputStream()
{
return someUserInputStream; // whatever your stream is
}
}
Since the concept of working with event streams is pretty modern I'm struggeling to find best practices for the bigger picture when designing applications with event streams.
Definitely, it's cool to be on the frontier, but it also means you have to discover "the right way" as you go along!
So, remember that ISubject is both an IObserver (the Publish part), and an IObservable (the Subscribe part) - if your intent is that your method's clients should be Subscribers, only give them the IObservable part (i.e. cast Subject to IObservable), and if your method's clients should be Publishers, give them the IObserver part.
You see the same thing when you use Observable.CreateWithDisposable(), where the thing you're given is the IObserver bit (since you're creating an IObservable, your job is to publish stuff!)
精彩评论