Modelの更新をリスンする
MVCでは、ViewはModelの変更をハンドリングしてUIを更新します。一般的に?、ModelはViewに依存しない(普通、View →依存→ Controller →依存→ Modelの関係になる)ので、リスナを使って間接的に依存する感じにします。Observerパターンというヤツです。
でも、これだとModelにaddListenner()とかを追加する必要があって気持ち悪い!
ModelじゃなくてControllerにaddListenner()とかを用意して、ControllerでModelを更新した際にキックする、という手もありますがあんまり好きではないのです。
- Modelの更新処理ごとにリスナをキックする処理を書くのがメンドイ。
- Modelを更新したのにリスナに通知してない、とかいうバグが起こりやすい。
- 変更を受けてリスナをキックする処理が散在する。(関数化すればいいんですけどね)
というわけで、Modelに簡単にリスナ機能を付加するユーティリティを書いてみました。
使い方
- Modelは普通のオブジェクトです。
- パラメータの変更は独自に定義したset()関数を利用して行います。(そういうルール)
- addListener()でModelにリスナを追加します。
- Modelの値をset()で更新すると、リスナ関数が実行されます。
// Model function Kitten( name, age ) { this.name = name; this.age = age; this.state = "stop"; } Kitten.prototype = { // Modelの更新はset関数を使って行うルール。 set: function( key, value ) { this[key] = value; } } // Modelを作成。 var mii = new Kitten( "mii", 1 ); // リスナを設定 addListener( mii, "state", function( state ) { document.getElementById("stdout1").innerHTML = state; } ); addListener( mii, "state", function( state ) { document.getElementById("stdout2").innerHTML = state; } ); addListener( mii, "state", function( state ) { document.getElementById("stdout3").innerHTML = state; } ); // Modelの状態が変更されると、リスナが実行される。 mii.set( "state", "waiting." ); mii.set( "state", "running." ); mii.set( "state", "stop." );
実装
- Modelにリスナを追加。
- set関数を置き換えて、リスナをキックするように修正しています。
/** * 更新リスナを追加する。 * @param {Object} target リスナを追加するモデル * @param {String} key イベントキー * @param {Function} listener リスナ関数 */ function addListener( target, key, listener ) { if ( !target.__listeners ) { // ターゲットを拡張する。 target.__listeners = {}; // リスナの記録先を確保。 var org = target.set; // setを上書き。変更を受けてリスナをキックする関数にする。 target.set = function( key, value ) { var result = null; if ( org ) { result = org(); } else { this[key] = value; } if ( this.__listeners[key] ) { for (var i=0; i < this.__listeners[key].length; i++ ) { this.__listeners[key][i]( value ); } } return result; } } // リスナをターゲットの属性として追加。 if ( !target.__listeners[key] ) { target.__listeners[key] = [] } target.__listeners[key].push( listener ); }