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

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

リスナ登録ユーティリティ

Modelの更新をリスンするで作ったリスナ登録ユーテイリティをDIコンテナで使えるようにしてみました。

  • DIコンテナに登録された関連づけ情報に従って、コンポーネントにリスナを設定します。
  • ユーティリティはEagerSingletonなので登録するだけでOK

以下はサンプル。モデル、ビュー、コントローラをDIコンテナに登録して関連づけています。モデル→ビューの関連づけでリスナ登録ユーティリティを利用。

// モデル / 状態を保持するだけのオブジェクト
function Kitten( ) {
  this.state = "stop";
}

// コントローラー
// API呼び出しを受けてモデルを更新する。
function KittenService( kitten ) {
  this.kitten = kitten;
}
KittenService.prototype = {
  run:  function() {
    this.kitten.set("state", "running"); // set関数で更新するルール
  },
  stop: function() {
    this.kitten.set("state", "stop");
  },
  jump: function() {
    this.kitten.set("state", "jump!");
  }
}

// ビュー / モデルの更新を受けてUIを更新する。
function KittenStateView( elm ) {
  this.elm = elm;
}
KittenStateView.prototype = {
  // 状態変更ハンドラ
  onStateChanged: function( state ) {
      document.getElementById( this.elm ).innerHTML = state;
  }
}

// コンテナ
var c = new container.Container( function ( binder ) {
    // モデル
    binder.bind("model").to( Kitten );
    // コントローラ
    binder.bind("ctrl").to( KittenService ).inject( {
        "kitten":container.component("model")
    } );
    // ビュー
    binder.bind("view1").to( KittenStateView ).inject( {"elm":"stdout1"} );
    binder.bind("view2").to( KittenStateView ).inject( {"elm":"stdout2"} );
    binder.bind("view3").to( KittenStateView ).inject( {"elm":"stdout3"} );

    // 関連づけユーティリティを登録 / EagerSingletonなので登録するだけでOK
    binder.bind("listener-binder").to( LitenerBinder );
    // 関連づけレシピを登録する。LitenerBinderはこれを見てリスナを設定する。
    binder.bind("listen@model#state").toFunction( "view1", "onStateChanged" );
    binder.bind("listen@model#state").toFunction( "view2", "onStateChanged" );
    binder.bind("listen@model#state").toFunction( "view3", "onStateChanged" );
});

// コントローラを作成。
var service = c.get("ctrl");
service.run(); // モデルの更新を受けてビューが更新される。

確認はこちらから。
なかなか疎結合ではないかと。コントローラーでset関数を使わないといけないのがちょっといやな感じですが、まぁ仕方ないよね。


ユーティリティの実装は次の通りです。

/**
 * コンポーネントのフィールドの変更を捕捉するリスナを設定する。
 */
LitenerBinder = function() {
    this.prefix = "listen"; // @inject
}
LitenerBinder.prototype = {
    meta: {
      "@Container": {
          "@Scope": container.Scope.EagerSingleton,
          "@Initialize": function( binder, container ) { binder.bind( container ); }
      }
    },

    /**
     * コンポーネント定義を元にモデルのリスナを設定する。
     * @param {Object} container
     */
    bind: function( container ) {
        var thiz = this;
        container.eachComponentDefs( function( key, list ) {
            var result = new RegExp(  thiz.prefix + "@(.*)#(.*)$").exec( key );
            if ( result && result.length >= 3 ) {
                var component = container.get( result[1] );
                var listeners = container.gets( key );
                if ( component && listeners && result[2]) {
                    thiz.addListeners( component, result[2], listeners );
                }
            }
        } );
    },

    /**
     * 更新リスナを追加する。
     * @param {Object} target リスナを追加するモデル
     * @param {String} key イベントキー
     * @param {Function} listener リスナ関数の配列
     */
    addListeners: function ( target, key, listeners ) {
      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;
        }
      }
      // リスナをターゲットの属性として追加。
      target.__listeners[key] = listeners;
    }
}