111
115

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

古典的オブザーバパターンをいまさら基礎からみる【デザインパターン】

Last updated at Posted at 2015-04-15

はじめに

  • 色々なサイトに散らばっている情報を整理しただけ。一部Unityぽい。
  • Rx流行ってるけど、そもそも根底のオブザーバパターンを理解しよう。

オブザーバパターンを使うと何が嬉しいのか?

  • Observer側もObservable側も相手のクラスの内部設計を把握しなくて良い。
  • 「データが変更されたらUIに反映する」というシナリオにおいて、 Model -> View という直接の依存関係を無くせる。

オブザーバパターンに頼らない場合

class Model
{
	// データ変更時に呼ばれる
	void DataChanged() {
		// 変更後のデータを取得
		var newData = this.GetData();
		// ModelからViewを直接変更(MVC原則に違反しています!)
		FooView.Update(newData);
	}
}
  • GUIクラス(View)の追加が発生した時に、データ処理クラス(Model)にも変更が発生してしまう。
  • Viewは見栄えをよくしたり、ちょっとしたカスタマイズをしたくなることが多い。それだけ、変更が発生しやすい部分といえる。そのためViewクラスとModelクラスは、なるべく結合度を弱めておく必要がある。 1

オブザーバパターンを使ったミニマルな例 2

/**
 * UI側の内部実装を知らなくても、 Update() というインターフェースだけ合意がとれていれば実装可
 * Oveservable側は、誰に観察されているかは知らなくて良い
 * ただ、観察されているという事実と、自分の状態に変化があった場合に、NotifyObservers()を
 * 呼び出して、すべてのObserverのUpdate()を呼び出してやるだけ
 */
class Model : Oveservable
{
	// サボってpublic
	public string Foo;

	void DataChanged() {
		Foo = this.GetData();
		// 変更を監視者(Observer)へ通知
		this.NotifyObservers();
	}
}

// Observableのどの状態が変更されたのか、問い合わせるだけで良い
//(後述のPush型ならば引数から取れる)
class View : IObserver
{
	public UILabel Label;
	private Model dataSource;

	public Model DataSource
	{
		set
		{
			// Subscribe, Add, RegisterObserver などメソッド名は色々
			value.AddObserver(this);
			dataSource = value;
		}
	}

	// Pull型の場合 (Unityの場合この名前じゃ駄目だけど)
	void Update()
	{
		Label.text = dataSource.Foo;
	}
}

class Program
{
	public Model Model;
	public View View;

	void Start()
	{
		View.DataSource = Model;
		// ... modelのデータを変更するような処理 ...
	}
}
  • GUIクラスが追加されてもデータ処理クラス側では一切変更の必要はなくなる

Push型とPull型 3

オブザーバパターンでは,タイミングの 通知 を主に置いているため,それ以上の情報を受け渡すには工夫がいります。この問題に対処するには伝統的に2つの方法があります。

一つは、updateの引数に変更された状態を渡すことで、Observerに通知する「 push 」型。
もう一つは、updateを呼び出されたタイミングでObserverがObservableに状態を問い合わせる「 pull 」型です。

push型の特徴

push型は、ObserverがObservableのどの状態が変更されたのかを直接知ることができるのが特徴です。updateの引数で渡されてきたものが、変更された状態だからです。短所は、updateメソッドに引数をとる形でインターフェースを定義しなければならないので、一般化に向かない点です。

// Push型の場合、Interfaceで型を指定しなければならない。
// 故に、全てのObserverクラスで型を一致させる必要がある。
// 一方で用途が限定されているときはシンプルに書ける
interface IObserver
{
	void Update(String value);
}

// IObserverを実装した例(先のミニマルな例と見比べると違いがわかる)
void Update(string text)
{
	Label.text = text;
}

pull型の特徴

pull型は、一般化した形でupdateメソッドを定義できます。しかし、Observableのどの状態が変更されたのかをObservableに問い合わせなければなりません。Observableに状態が非常にたくさんある場合は、なんらかの方法を使わなければ、複雑になってしまいます。

pushとpullの使い分け

「push型」と「pull型」のどちらを使うかは、単純にObservableの状態の複雑さで決めればいいと思います。Observableの状態が単純でならばpull型。Observableの状態が複雑で、多くのフィールドを持つような場合はpush型にするといいと思います。

参考資料

おわりに

  • 勉強がてら書いているので何か間違いがあった場合、ご指摘下さると幸いです。
  1. 事例で学ぶデザインパターン 第5回 Observer パターン より

  2. フレームワーク部は省略。参考資料を参照されたい

  3. 実践デザパタ-その14:Observerパターンより。一部改変

111
115
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
111
115

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?