2

I want to create a class that can be used to represent a dynamically computed value, and another class that represents a value can be the source (subject) for these dynamically computed values. The goal is that when the subject changes, the computed value is updated automatically.

It seems to me that using IObservable/IObserver is the way to go. Unfortunately I can't use the Reactive Extensions library, so I am forced to implement the subject/observer pattern from scratch.

Enough blabla, here are my classes:

public class Notifier<T> : IObservable<T>
{
    public Notifier();
    public IDisposable Subscribe(IObserver<T> observer);
    public void Subscribe(Action<T> action);
    public void Notify(T subject);
    public void EndTransmission();
}

public class Observer<T> : IObserver<T>, IDisposable
{
    public Observer(Action<T> action);
    public void Subscribe(Notifier<T> tracker);
    public void Unsubscribe();
    public void OnCompleted();
    public void OnError(Exception error);
    public void OnNext(T value);
    public void Dispose();
}

public class ObservableValue<T> : Notifier<T>
{
    public T Get();
    public void Set(T x);
}

public class ComputedValue<T>
{
    public T Get();
    public void Set(T x);
}

My implementation is lifted mostly from: http://msdn.microsoft.com/en-us/library/dd990377.aspx.

So what would the "right" way to do this be? Note: I don't care about LINQ or multi-threading or even performance. I just want it to be simple and easy to understand.

4

1 に答える 1

11

私があなただったら、Rx が実装されている方法にできる限り近い方法でクラスを実装しようとします。

重要な基本原則の 1 つは、多数の操作を使用して結合される比較的少数の具象クラスを使用することです。したがって、いくつかの基本的なビルディング ブロックを作成し、コンポジションを使用してそれらをすべてまとめる必要があります。

Reflector.NET の下で最初に確認する 2 つのクラスがあります: AnonymousObservable<T>& AnonymousObserver<T>。特にAnonymousObservable<T>、オブザーバブルをインスタンス化するための基礎として Rx 全体で使用されます。実際、そこから派生するオブジェクトを見ると、IObservable<T>特殊な実装がいくつかありますが、それAnonymousObservable<T>は汎用目的のみです。

静的メソッドObservable.Create<T>()は本質的に へのラッパーAnonymousObservable<T>です。

要件に明らかに適合する他の Rx クラスはBehaviorSubject<T>. サブジェクトはオブザーバブルとオブザーバーの両方BehaviorSubjectであり、受け取った最後の値を記憶しているため、状況に適合します。

これらの基本的なクラスが与えられれば、特定のオブジェクトを作成するために必要なすべての要素がほぼ揃います。オブジェクトは上記のコードから継承するべきではありませんが、代わりにコンポジションを使用して必要な動作をまとめてください。

ここで、Rx との互換性を高め、より構成可能で堅牢にするために、クラス設計にいくつかの変更を加えることをお勧めします。

Notifier<T>を使用することを優先して、クラスを削除しますBehaviourSubject<T>

Observer<T>を使用することを優先して、クラスを削除しますAnonymousObserver<T>

次に、次ObservableValue<T>のように変更します。

public class ObservableValue<T> : IObservable<T>, IDisposable
{
    public ObservableValue(T initial) { ... }
    public T Value { get; set; }
    public IDisposable Subscribe(IObserver<T> observer);
    public void Dispose();
}

の実装は、メンバーを公開すると&へのアクセスが許可されるため、継承するのではなくObservableValue<T>ラップします。このクラスは計算ではなく値を表すため、あまり意味がありません。サブスクリプションは、ラップされたを使用してクリーンアップします。BehaviourSubject<T>IObserver<T>OnCompletedOnErrorAnonymousObservable<T>DisposeBehaviourSubject<T>

次に、次ComputedValue<T>のように変更します。

public class ComputedValue<T> : IObservable<T>, IDisposable
{
    public ComputedValue(IObservable<T> source) { ... }
    public T Value { get; }
    public IDisposable Subscribe(IObserver<T> observer);
    public void Dispose();
}

このComputedValue<T>クラスはAnonymousObservable<T>、すべてのサブスクライバーに対してラップsourceし、プロパティの値のローカル コピーを取得するために使用しValueます。このメソッドは、オブザーバブルDisposeからサブスクライブを解除するために使用されます。source

これらの最後の 2 つのクラスは、設計に必要と思われる唯一の実際の特定の実装です。これは、Valueプロパティによるものです。

ObservableValues次に、拡張メソッドの静的クラスが必要です。

public static class ObservableValues
{
    public static ObservableValue<T> Create<T>(T initial)
    { ... }

    public static ComputedValue<V> Compute<T, U, V>(
        this IObservable<T> left,
        IObservable<U> right,
        Func<T, U, V> computation)
    { ... }
}

Computeメソッドは、 を使用してAnonymousObservable<V>計算を実行し、メソッドによって返されるIObservable<V>のコンストラクターに渡す を生成します。ComputedValue<V>

これらすべてが整ったら、次のコードを記述できます。

var ov1 = ObservableValues.Create(1);
var ov2 = ObservableValues.Create(2);
var ov3 = ObservableValues.Create(3);

var cv1 = ov1.Compute(ov2, (x, y) => x + y);
var cv2 = ov3.Compute(cv1, (x, y) => x * y);

//cv2.Value == 9

ov1.Value = 2;
ov2.Value = 3;
ov3.Value = 4;

//cv2.Value == 20

これが役立つかどうか、および/または私が詳しく説明できることがあればお知らせください。


編集:使い捨ても必要です。

また、特に拡張メソッドでサブスクリプションを管理するために実装する必要がありますAnonymousDisposable。Reflector.NET を使用して Rx の実装を確認するか、以下の私のバージョンを使用してください。CompositeDisposableCompute

public sealed class AnonymousDisposable : IDisposable
{
    private readonly Action _action;
    private int _disposed;

    public AnonymousDisposable(Action action)
    {
        _action = action;
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref _disposed, 1) == 0)
        {
            _action();
        }
    }
}

public sealed class CompositeDisposable : IEnumerable<IDisposable>, IDisposable
{
    private readonly List<IDisposable> _disposables;
    private bool _disposed;

    public CompositeDisposable()
        : this(new IDisposable[] { })
    { }

    public CompositeDisposable(IEnumerable<IDisposable> disposables)
    {
        if (disposables == null) { throw new ArgumentNullException("disposables"); }
        this._disposables = new List<IDisposable>(disposables);
    }

    public CompositeDisposable(params IDisposable[] disposables)
    {
        if (disposables == null) { throw new ArgumentNullException("disposables"); }
        this._disposables = new List<IDisposable>(disposables);
    }

    public void Add(IDisposable disposable)
    {
        if (disposable == null) { throw new ArgumentNullException("disposable"); }
        lock (_disposables)
        {
            if (_disposed)
            {
                disposable.Dispose();
            }
            else
            {
                _disposables.Add(disposable);
            }
        }
    }

    public IDisposable Add(Action action)
    {
        if (action == null) { throw new ArgumentNullException("action"); }
        var disposable = new AnonymousDisposable(action);
        this.Add(disposable);
        return disposable;
    }

    public IDisposable Add<TDelegate>(Action<TDelegate> add, Action<TDelegate> remove, TDelegate handler)
    {
        if (add == null) { throw new ArgumentNullException("add"); }
        if (remove == null) { throw new ArgumentNullException("remove"); }
        if (handler == null) { throw new ArgumentNullException("handler"); }
        add(handler);
        return this.Add(() => remove(handler));
    }

    public void Clear()
    {
        lock (_disposables)
        {
            var disposables = _disposables.ToArray();
            _disposables.Clear();
            Array.ForEach(disposables, d => d.Dispose());
        }
    }

    public void Dispose()
    {
        lock (_disposables)
        {
            if (!_disposed)
            {
                this.Clear();
            }
            _disposed = true;
        }
    }

    public IEnumerator<IDisposable> GetEnumerator()
    {
        lock (_disposables)
        {
            return _disposables.ToArray().AsEnumerable().GetEnumerator();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public bool IsDisposed
    {
        get
        {
            return _disposed;
        }
    }
}
于 2011-09-26T02:12:44.727 に答える