Observerパターン

Observerパターン

Observerパターンとは、「クラスから通知を発行する仕組み」と、「他のクラスから発行された通知を受け取る仕組み」を実現するためのデザインパターンです。
主に、以下のような状況において特に有用です。

  1. 双方向ではなく、一方からもう一方へと一方通行でデータを発行する場合

  2. 1回ではなく、複数回データを発行する場合

  3. 任意のタイミングでデータを発行したい場合

  4. 発行されたデータを、複数のクラスが同時に受け取りたい場合

ざっくりいいかえれば、「いつ起こるかわからない処理」に有用。

ObserverとObservable

Observer・・・「データを受け取るクラス」のこと。監視者。
Observable・・・「データを発行するクラス」のこと。監視されうるもの。

基本的な仕組み

  1. ObserverがObservableにデータの発行先として登録する。

  2. Observableは登録されたすべての発行先に値を発行する

  3. Observerは受け取った値を使用して任意の処理を実行する

1.ObserverがObservableにデータの発行先として登録する。

// observerA が Observableにデータの発行先として登録
observable.Subscribe(observerA);

// observerB が Observableにデータの発行先として登録
observable.Subscribe(observerB);

ObserverがObservableにデータの発行先として登録することを、「購読(Subscribe)する」と言います。

※「AさんはS社の雑誌を定期購読する」といった表現からくると思われる。

2.Observableは登録されたすべての発行先に値を発行する

Observableは、発行する値ができると、値の発行先として登録されたすべてのObserverに対して一斉に値を発行します

// すべてのObserverに対して一斉にvalueを発行
observer.OnNext(value);

3.値を受け取ったときのコールバック処理を記述する

各メソッドにそれぞれ通知が来たときに実行したい処理(いつ起こるかわからない処理)を自由に記述します。

// .NET Framework4.0以降には、Observerパターンで使えるIObserver<T>インターフェイスとIObservable<T>インターフェイスがSystem名前空間に標準で用意されています。

public class Observer : IObserver<int>
{
    private string m_name;

    public Observer(string name)
    {
        m_name = name;
    }

    public void OnCompleted()
    {
        Console.WriteLine($"通知の受け取りが完了しました");
    }

    public void OnError(Exception error)
    {
        Console.WriteLine($"次のエラーを受信しました:{error.Message}");
    }

    public void OnNext(int value)
    {
        Console.WriteLine($"{value}を受け取りました");
    }
}

Observableの実装例

public class Observable : IObservable<int>
{
    //購読されたIObserver<int>のリスト
    private List<IObserver<int>> m_observers = new List<IObserver<int>>();

    public IDisposable Subscribe(IObserver<int> observer)
    {
        if(!m_observers.Contains(observer))
            m_observers.Add(observer);
        //購読解除用のクラスをIDisposableとして返す
        return new Unsubscriber(m_observers, observer);
    }

    public void SendNotice()
    {
        //すべての発行先に対して1,2,3を発行する
        foreach (var observer in m_observers)
        {
            observer.OnNext(1);
            observer.OnNext(2);
            observer.OnNext(3);
        }
    }

    //購読解除用内部クラス
    private class Unsubscriber : IDisposable
    {
        //発行先リスト
        private List<IObserver<int>> m_observers;
        //DisposeされたときにRemoveするIObserver<int>
        private IObserver<int> m_observer;

        public Unsubscriber(List<IObserver<int>> observers, IObserver<int> observer)
        {
            m_observers = observers;
            m_observer = observer;
        }

        public void Dispose()
        {
            //Disposeされたら発行先リストから対象の発行先を削除する
            m_observers.Remove(m_observer);
        }
    }
}

使用例

class Program
{
    static void Main(string[] args)
    {
        //値を受け取るクラスを3つ作成
        Observer observerA = new Observer("Aさん");
        Observer observerB = new Observer("Bさん");
        Observer observerC = new Observer("Cさん");

        //値を発行するクラスを作成
        Observable observable = new Observable();

        //3つのObserverが、自分自身を発行先として登録する(=購読)
        IDisposable disposableA = observable.Subscribe(observerA);
        IDisposable disposableB = observable.Subscribe(observerB);
        IDisposable disposableC = observable.Subscribe(observerC);
        Console.WriteLine("Aさん〜Cさんが値を購読しました");

        Console.WriteLine("値を発行させます");
        //Observableに値を発行させる
        observable.SendNotice();

        Console.ReadKey();
    }
}

この記事が気に入ったらサポートをしてみませんか?