はじめに
- 色々なサイトに散らばっている情報を整理しただけ。一部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型にするといいと思います。
参考資料
- 実践デザパタ-その14:Observerパターン
- 事例で学ぶデザインパターン 第5回 Observer パターン
- [C#][Design Pattern] C# による Observer パターンの実装
おわりに
- 勉強がてら書いているので何か間違いがあった場合、ご指摘下さると幸いです。
-
フレームワーク部は省略。参考資料を参照されたい ↩
-
実践デザパタ-その14:Observerパターンより。一部改変 ↩