無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Modelの更新をリスンする

MVCでは、ViewはModelの変更をハンドリングしてUIを更新します。一般的に?、ModelはViewに依存しない(普通、View →依存→ Controller →依存→ Modelの関係になる)ので、リスナを使って間接的に依存する感じにします。Observerパターンというヤツです。

でも、これだとModelにaddListenner()とかを追加する必要があって気持ち悪い!
ModelじゃなくてControllerにaddListenner()とかを用意して、ControllerでModelを更新した際にキックする、という手もありますがあんまり好きではないのです。

  • Modelの更新処理ごとにリスナをキックする処理を書くのがメンドイ。
  • Modelを更新したのにリスナに通知してない、とかいうバグが起こりやすい。
  • 変更を受けてリスナをキックする処理が散在する。(関数化すればいいんですけどね)

というわけで、Modelに簡単にリスナ機能を付加するユーティリティを書いてみました。

使い方
  1. Modelは普通のオブジェクトです。
    • パラメータの変更は独自に定義したset()関数を利用して行います。(そういうルール)
  2. addListener()でModelにリスナを追加します。
  3. 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  );
}