问题
I read a comment on this web site about how to use IObservable that said..
As a general rule (guideline), I strongly suggest not having an IObservable<T>
as a parameter to a method. The obvious caveat being if that method is a new Rx Operator e.g. Select
, MySpecialBuffer
, Debounce
etc.
So I've been trying to apply this advice to my code and keep running into situations where it seems convenient to break this rule. Take a look at the code below and compare PersonSelectorViewModelA
and PersonSelectorViewModelB
. These view models might be bound to the ItemsSource and SelectedValue properties of a ComboBox.
I like PersonSelectorViewModelA
- which takes an IObservable in the constructor - better because once it is constructed it automatically responds to people being added to and from the address book. It takes care of its business and responsibilities without any babysitting. PersonSelectorViewModelB
requires more "maintenance and care" since code that instantiates it needs to remember to periodically call UpdatePeople
.
So which is a better approach?
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid Id { get; set; }
}
public interface IAddressBookRepository
{
void AddOrUpdatePerson(Person p);
void DeletePerson(Guid id);
IObservable<IEnumerable<Person>> People();
}
public class PersonSelectorViewModelA : BindableBase
{
Guid? _selectedPersonId = null;
ObservableCollection<Person> _peopleCollection = new ObservableCollection<Person>();
IObservable<IEnumerable<Person>> _peopleSource;
IDisposable _subscription;
public PersonSelectorViewModelA(IObservable<IEnumerable<Person>> people)
{
_peopleSource = people;
}
public void OnNavigateTo()
{
_subscription =
_peopleSource
.Select(i => i.ToArray())
.Subscribe(i =>
{
_peopleCollection.Clear();
foreach (var p in i)
{
_peopleCollection.Add(p);
}
if (!i.Any(j => j.Id == SelectedPersonId))
{
SelectedPersonId = null;
}
});
}
public void OnNavigateAway() => _subscription?.Dispose();
public Guid? SelectedPersonId {
get { return _selectedPersonId; }
set { SetProperty(ref _selectedPersonId, value); }
}
public ObservableCollection<Person> People => _peopleCollection;
}
public class PersonSelectorViewModelB : BindableBase
{
Guid? _selectedPersonId = null;
ObservableCollection<Person> _peopleCollection = new ObservableCollection<Person>();
public void UpdatePeople(IEnumerable<Person> people)
{
_peopleCollection.Clear();
foreach (var p in people)
{
_peopleCollection.Add(p);
}
if (!people.Any(j => j.Id == SelectedPersonId))
{
SelectedPersonId = null;
}
}
public Guid? SelectedPersonId {
get { return _selectedPersonId; }
set { SetProperty(ref _selectedPersonId, value); }
}
public ObservableCollection<Person> People => _peopleCollection;
}
回答1:
In the case of a reactive ICommand, the command host will often know when to enable and disable the command and could do that manually (externally) rather than constructing an IObservable just to then pass it into the command's constructor. Same goes for reactive FullName property that displays a concatenation of FirstName and LastName; simpler to make FullName an IObserver rather than building an IObservable and passing it into the constructor of the FullName property. So for these examples I see your point.
My data layer has functions like IObservable<Person[]> GetPeople(string city)
and IObservable<Person> GetPerson(Guid id)
so views can display up-to-date info. This layer doesn't know who its consumers are; there are many view models that need this data. Suppose one consumer is a "people picker" view model hosted by another view model X. Rather than have X subscribe to the observable and externally drive the "people picker" that it owns, I give the "people picker" the observable in its constructor so X can be more hands off. X doesn't care about how the people picker does its job - it just wants it to do it. X "knows about" the observable, but only so much as is necessary to grab it from some data layer and use it to construct the people picker.
I think this is similar to a WPF ListView. You can set the ItemsSource property of a ListView to a collection that supports INotifyCollectionChanged and then the ListView subscribes to events and automatically updates its contents. This data binding is simpler than having the view model that owns the ListView subscribe to list changes and call some UpdateItems method on the ListView.
It sounds like you are saying to almost never pass an observable to the constructor of an object. So thinking in terms of constructor dependency injection, a class shouldn't depend on an observable sequence. But this can't be right and is probably not what you are saying. Maybe I didn't properly explain the scenario in the original question. Otherwise a class could only depend on observables it creates (weird) or observables it grabs through method calls to its dependencies (similar to constructor injection).
回答2:
My standard advice still holds. Your code works, so this is my opinion. I believe that making a computer do what you want isn't very hard at all, however having another human understand what you wer trying to gett the computer to do is far harder.
I think you either have option B (dumb VM that gets updated externally), or you pass in the IAddressBookRepository
.
But passing in an IObservable<T>
IMO is just weird.
If we consider that an IObservable<T>
is just Rx's implementation of a callback/observer pattern.
Next we know that the observer pattern is for when we want a system that we depend on, to call us back, with out explicitly knowing about us.
However if we are passing in an IObservable<T>
, then the thing passing it in clearly knows about both the IObservable<T>
and then thing it is creating.
So what is a callback mechanism buying us here expect for double the indirection?
来源:https://stackoverflow.com/questions/42569678/passing-an-iobservable-to-a-constructor-good-idea