読者です 読者をやめる 読者になる 読者になる

Observerパターン

C# programming

社内でアジャイルプラクティス読書会をやっていたのですが(http://d.hatena.ne.jp/ZOETROPE/20080513#1210682874)、約2ヶ月かけて全部読み終わりました。

この読書会は結構好評だったので、続けていきたいねという話になり、じゃあ次はデザインパターンをやってみようということになりました。

第1回は僕が発表することになったので、デザインパターンの本を読み直し、Observerパターンについて簡単にまとめてみました。

悪魔の囁き

「情報が必要になったときは、情報を持っているオブジェクトからゲットすればいいのさ!」

Observerパターンとは

  • オブジェクトの状態変化を 他のオブジェクトに通知する ためのデザインパターン
  • Publish-Subscribeパターンとも呼ばれる。

Observerパターンのモデル

登場人物 役割
Subject 観察対象
ConcreteSubject 具体的な観察対象
Observer 観察者
ConcreteObserver 具体的な観察者

SubjectにObserverをregisterObserverメソッドで登録しておき、Subjectの状態が変化したら、Observerのupdateメソッドを使って通知するというパターンです。

サンプルプログラム

増補改訂版Java言語で学ぶデザインパターン入門のサンプルプログラムを、C#で書き直したものです。

using System;
using System.Collections.Generic;

public class Program
{
    static void Main(string[] args)
    {
        //観察者の生成
        IObserver o1 = new DigitObserver();
        IObserver o2 = new GraphObserver();

        //観察対象の生成
        NumberGenerator gen = new RandomNumberGenerator();

        //観察者の登録
        gen.RegisterObserver(o1);
        gen.RegisterObserver(o2);

        //数値の生成
        for (int i = 0; i < 20; i++)
        {
            gen.Execute();
            System.Threading.Thread.Sleep(500);
        }
        Console.ReadLine();
    }
}

/// <summary>
/// 数値を生成する抽象クラス
///   観察対象(Subject)
/// </summary>
public abstract class NumberGenerator
{
    /// <summary>
    /// 観察者のリスト
    /// </summary>
    private List<IObserver> observers = new List<IObserver>();

    /// <summary>
    /// 観察者の追加
    /// </summary>
    /// <param name="o"></param>
    public void RegisterObserver(IObserver o)
    {
        observers.Add(o);
    }

    /// <summary>
    /// 観察者の削除
    /// </summary>
    /// <param name="o"></param>
    public void RemoveObserver(IObserver o)
    {
        observers.Remove(o);
    }

    /// <summary>
    /// 観察者への通知
    /// </summary>
    public void NotifyObservers()
    {
        foreach (IObserver o in observers)
        {
            o.Update(GetNumber());
        }
    }

    /// <summary>
    /// 数値の取得
    /// </summary>
    /// <returns>取得した数値</returns>
    public abstract int GetNumber();

    /// <summary>
    /// 数値の生成
    /// </summary>
    public abstract void Execute();
}

/// <summary>
/// ランダムな数値を生成するクラス
///   具体的な観察対象(ConcreteSubject)
/// </summary>
public class RandomNumberGenerator : NumberGenerator
{
    private Random random = new Random();
    private int number;
    public override int GetNumber()
    {
        return number;
    }
    public override void Execute()
    {
        number = random.Next(50); 
        NotifyObservers();
    }
}

/// <summary>
/// 観察者を表現するインタフェース
///   観察者(Observer)
/// </summary>
public interface IObserver
{
    void Update(int num);
}

/// <summary>
/// 観察している値を数字で表示する観察者
///   具体的な観察者(ConcreteObserver)
/// </summary>
public class DigitObserver : IObserver
{
    public void Update(int num)
    {
        Console.WriteLine("DigitObserver:{0}", num);
    }
}

/// <summary>
/// 観察している値をグラフで表示する観察者
///   具体的な観察者(ConcreteObserver)
/// </summary>
public class GraphObserver : IObserver
{
    public void Update(int num)
    {
        Console.Write("GraphObserver:");
        for (int i = 0; i < num; i++)
        {
            Console.Write("*");
        }
        Console.WriteLine();
    }
}

天使の助言

「相互にやりとりするオブジェクト間は、疎結合となるように設計しなさい。」

こんな気分

  • 新しいObserverが欲しくなったら、いつでも追加できる。
  • Observerを追加した場合でも、Subjectは変更しなくてよい。
  • ObserverとSubjectを、容易に置き換えたり、再利用したりすることができる。

バランスが肝心

  • 観察対象が単純ならば、pullモデルで充分である。
  • Observerパターンを乱用すると、流れが追いかけにくくなる。
  • あまりに細かく通知されると、煩わしいこともある。
  • Javaではjava.util.Observableを、C#ではEventを利用できないか検討してみる。
  • ObserverのアクションがSubjectの状態に影響を与えるときは、無限呼び出しにならないように注意する。

参考文献

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門


Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本


アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技


アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

「悪魔の囁き」「天使の助言」「こんな気分」「バランスが肝心」という項目は、アジャイルプラクティスを参考にさせてもらっています。
このまとめ方は、いろんなところに応用できて良いですね。