如何将一组动态Observable组合成一个新的Observable?

时间:2015-09-01 15:38:14

标签: .net parent-child system.reactive observablecollection

我的程序中有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来监控添加和删除,但我无法重新创建可观察的内容,但由于有人订阅,这显然无效已经改变了。

1 个答案:

答案 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来处置连接。这是正确的方法 - 您应尽可能避免使用主题。他们只会使你的代码难以使用。