我的程序中有Room
的概念。每个房间都有一些User
个,每个用户都有一个Observable
,表明他想要在房间里达到什么样的沉默。当用户选择新值时,我只需通过调用OnNext
类内部BehaviorSubject
实例上的User
将其推送到Observable。
该计划的主要逻辑是,一个人应该能够告诉"什么程度的沉默"房间需要。例如,如果房间里至少有一个用户需要保持沉默,那么整个房间都需要保持沉默。
这是我课程的简化:
首先,我使用枚举来表示可能的声级。现在只有两个值:
enum SoundLevelRequirement
{
/// <summary>
/// Indicates that the user is ok with any sound level in the room.
/// </summary>
None,
/// <summary>
/// Indicates that the user needs absolute silence in the room.
/// </summary>
Silence
}
用户只需公开一个Observable,只要状态发生变化就会打开。
class User
{
private readonly BehaviorSubject<SoundLevelRequirement> soundLevel;
public User(string name)
{
Name = name;
soundLevel = new BehaviorSubject<SoundLevelRequirement>(SoundLevelRequirement.None);
}
public string Name { get; }
public IObservable<SoundLevelRequirement> SoundLevelChanged => soundLevel.DistinctUntilChanged();
public void SetSoundLevel(SoundLevelRequirement level)
{
soundLevel.OnNext(level);
}
}
房间类基本上是用户的容器,应该有它自己的可观察性来代表整个房间的整体状态:
class Room
{
private readonly BehaviorSubject<SoundLevelRequirement> soundLevel;
private readonly ObservableCollection<User> users;
public Room(string name)
{
Name = name;
Users = new ObservableCollection<User>();
// THIS IS WHERE I NEED TO COMBINE ALL CHILD OBSERVABLES INSTEAD
// OF USING ANOTHER Subject
soundLevel = new BehaviorSubject<SoundLevelRequirement>(SoundLevelRequirement.None);
}
public ObservableCollection<User> Users { get; set; }
public string Name { get; }
public IObservable<SoundLevelRequirement> SoundLevel => soundLevel.DistinctUntilChanged();
}
由于解决方案的动态特性,我很难将使用Rx的用户Observable
直接合并到另一个Observable
中。我的主要问题是用户可以随时离开或加入房间(可以在房间中添加或删除新的User
个对象),因此我没有静态的可观察对象列表可供使用。如果我有一个静态列表,那么通过利用CombineLatest
然后对其进行过滤逻辑就可以很容易地实现,如下所示:
Users.Select(u => u.SoundLevelChanged).CombineLatest().Select(latest => latest.Max());
这样,无论何时任何用户状态发生变化,我都必须看到&#34;最大值&#34;是确定房间的状态。但是一旦我添加或删除用户,我需要将其与observable保持同步,所以这不起作用。
我还需要确保在用户适当地离开房间时进行处理。例如,如果一个5人的房间在&#34; Silence&#34;由于一个用户选择了Silence
,我必须在用户离开时重置房间状态并将其设置回None
。
我曾考虑使用ObservableCollection
来监控添加和删除,但我无法重新创建可观察的内容,但由于有人订阅,这显然无效已经改变了。
答案 0 :(得分:2)
如果可以的话,我会尽量让这个变得简单。您需要对类User
进行简单的更改才能执行此操作。
用户需要添加此属性:
public SoundLevelRequirement SoundLevel => soundLevel.Value;
现在您可以像这样实施Room
:
class Room
{
private readonly IObservable<SoundLevelRequirement> soundLevel;
public Room(string name)
{
this.Name = name;
this.Users = new ObservableCollection<User>();
soundLevel =
Observable
.Create<SoundLevelRequirement>(o =>
Observable
.FromEventPattern<
NotifyCollectionChangedEventHandler,
NotifyCollectionChangedEventArgs>(
h => this.Users.CollectionChanged += h,
h => this.Users.CollectionChanged -= h)
.SelectMany(x => this.Users
.Select(u => u.SoundLevelChanged
.StartWith(u.SoundLevel))
.Merge())
.Select(ep =>
this.Users.Any(u =>
u.SoundLevel == SoundLevelRequirement.Silence)
? SoundLevelRequirement.Silence
: SoundLevelRequirement.None)
.DistinctUntilChanged()
.Subscribe(o));
}
public ObservableCollection<User> Users { get; set; }
public string Name { get; }
public IObservable<SoundLevelRequirement> SoundLevel => soundLevel.AsObservable();
}
如果您需要让Room.SoundLevel
重播为1,请将.Replay(1)
添加到soundLevel
字段,但必须成为IConnectableObservable<SoundLevelRequirement>
那工作。然后,您需要在IDisposable
上实施Room
来处置连接。这是正确的方法 - 您应尽可能避免使用主题。他们只会使你的代码难以使用。