Skip to content
uupaa edited this page Nov 15, 2013 · 1 revision

Postal を使わないコード

以下は、数字を表示し [+][-] ボタンで数値を加減するコードです。

var _counter = 0; // global

// << Application >>
function Application() {
    document.querySelector("#plus").addEventListener("click", function(event) {
        document.querySelector("#counter").innerText = (++_counter);
    });
    document.querySelector("#minus").addEventListener("click", function(event) {
        document.querySelector("#counter").innerText = (--_counter);
    });
}

window.addEventListener("load", function() {
    document.body.innerHTML +=
            '<div id="counter">' + _counter + '</div>' +
            '<input type="button" id="plus" value="+" />' +
            '<input type="button" id="minus" value="-" />';

    var app = new Application();
});

Postal と 古典的MVCを使ったコード

上記のコードを MVC 化し、イベント通知部分に Postal を導入した例を見てみましょう。

と、その前に、役割分担を整理します。

  • グローバル変数 _counter は Model が管理するデータになります。
  • #plus, #minus ボタンの構築と、ユーザアクションに対してフィードバックを行う部分は View の担当になります。
    • ボタンが押されたら、押された事を View は Controller に通知します。
    • Controller は押されたボタンにより、Model を更新し、View を更新します。
  • Model から View に対する通知はメッセージを使います。このメッセージを "MV_UPDATE" とします。
  • View から Controller に対するクリック通知はメッセージを使います。このメッセージは "VC_CLICK_{button_id}" とします。button_id には "plus" または "minus" が入ります。

Model

Model は、_counter を内部に保持し、 Model#plus で _counter を増加, Model#minus で減少させます。

  • Postal を使ってメッセージ("MV_UPDATE")をポストしますが、誰がメッセージを受け取るかは実際のところ分かっていません(疎結合)
  • View の気配は感じていますが Model は View を知りません(疎結合)
  • Controller の気配は感じていますが Model は Controller を知りません(疎結合)
  • Model#plus, Model#minus, Model#getData メソッドを公開しています
  • View や Controller からメッセージを受け取らないため、inbox メソッドは実装されてません

このように、Model は View や Controller を知りません。
Model は、幾つかのメソッドと"MV_UPDATE" というメッセージを送信するだけに留まっています。

// << Model >>
function Model(postal) {
    this._postal = postal;
    this._counter = 0; // model data
}
Model.prototype.getData = function() {
    return { counter: this._counter };
};
Model.prototype.plus = function() {
    ++this._counter;
    this._postal.omit(this).post("MV_UPDATE"); // 自分以外(View, Controller)にメッセージを投げる
};
Model.prototype.minus = function() {
    --this._counter;
    this._postal.omit(this).post("MV_UPDATE"); // 自分以外(View, Controller)にメッセージを投げる
};

View

View は、ユーザアクションに反応し、必要に応じて表示を更新します。表示する内容は Model から取得します。

  • [+] ボタンと、[-] ボタンにイベントハンドラを設定し、クリックされるのを待ちます。
    • クリックで View#handleEvent がブラウザからコールバックされます。
  • View#handleEventの中で、VC_CLICK_{button_id} メッセージを送信します。
    • このメッセージに反応するのは Controller#inbox です。
    • Model は inbox を持っていないためメッセージが届きません。
  • View#inbox で "MV_UPDATE" メッセージを受け取ったら、画面を更新します(これはModelから発行されます)
    • "MV_UPDATE" メッセージは、Model から送信されます。
  • Model の存在を知っています。
  • Model#getData() でモデルの蓄えているデータを取得できる事を知っています。
  • Controller の存在を直接は知りませんが、Postal を通じて投げた VC_CLICK_{button_id} は結果的に Controller が拾います。

View は Controller を知りませんが、Model の事は知っています。
また Model から更新に必要なデータを取得できる事も知っています。

// << View >>
function View(postal, model) {
    this._postal = postal;
    this._model = model;

    document.querySelector("#plus").addEventListener("click", this, false);  // -> View#handleEvent
    document.querySelector("#minus").addEventListener("click", this, false); // -> View#handleEvent
}
View.prototype.handleEvent = function(event) {
    this._postal.omit(this).send("VC_CLICK_" + event.target.id); // call Controller.inbox
};
View.prototype.inbox = function(message) {
    if (message === "MV_UPDATE") { this.update(); }
};
View.prototype.update = function() {
    document.querySelector("#counter").innerText = this._model.getData().counter;
};

Controller

Controller は、View からの通知を受けて、Model または View を操作します。

  • View から発行される VC_CLICK_{button_id} を受け取り、 Model#plus と Model#minus をコールします。
  • Controller は View の存在を知っています(この例では持っているだけで使っていません)。
  • Controller は Model の存在を知っています。
  • Controller は Postal の参照を持っていません。
// << Controller >>
function Controller(view, model) {
    this._view = view;
    this._model = model;
}
Controller.prototype.inbox = function(message) {
    if (/^VC_CLICK_/.test(message)) {
        var button_id = msg.split("_").pop();

        switch (button_id) {
        case "plus":  this._model.plus(); break;
        case "minus": this._model.minus();
        }
    }
};

App

App は、Model, View, Controller のインスタンスを作成し、Postal への登録を行います。

  • Postal のインスタンスを、Model と View に渡しています。
  • Model と View は Postal を通じて、他のオブジェクトにメッセージを送信できます。
  • Postal#register で View と Controller をメッセージの送信先として登録しています。
    • Model は登録しません。
// << App >>
function App() {
    this._buildSurface();
    this._initApp();
}
App.prototype._buildSurface = function() {
    document.body.innerHTML += '<div id="counter">0</div>' +
                               '<input type="button" id="plus" value="+1" />' +
                               '<input type="button" id="minus" value="-1" />';
};
App.prototype._initApp = function() {
    var postal     = new Postal();
    var model      = new Model(postal);
    var view       = new View(postal, model);
    var controller = new Controller(view, model);

    postal.register(view).register(controller);
};

window.addEventListener("load", function() {
    var app = new App();
});

いかがだったでしょうか。

この例では、元々のコードがかなりシンプル(約20行)だったにも関わらず、
MVCの導入によって(残念ながら)コード量が3倍(約60行)に増えてしまいました。

不適切な例をひっぱり出して長々と説明しただけのようにも見えますが、
Postal を導入すると、

  • Model は View を知らない
  • Model は Controller を知らない
  • View は Controller を知らない

といった疎な状態になっています。

開発規模にもよりますが、メッセージを規約で先に決めてしまう事により、
それぞれの領域について設計や開発を並列で進める事も可能になります。

多人数での開発が予想される場合や、結合強度,保守性について解決しなければならない場合に、 Postal の導入を検討してみてください。

Clone this wiki locally