孤独プログラマー譚

孤独死が近い。

Observerパターンについて考える

Observerパターン、名前は知ってるけど、いまいち使いどころがよく分かりませんでした。

「状態の変化を通知する仕組み」とのことですが、あまりそういう状況に出くわさないというか、頭にピンとこないというか…。

調べてみたんですが、おそらく自分は勘違いしてました。

これ、別に通知とか関係ないのでは。

結局のところ、

<?php
$objects = [
    new ObjectA(),
    new ObjectB(),
    new ObjectC()
];
foreach ($objects as $object) {
    $object->commonMethod();
}

上記のように、「同名のメソッドを持つなら、ループできるよ」というだけのように思います。

<?php
interface Object
{
    public function commonMethod();
}

インターフェースを作って、共通のメソッドを持たせます。


そしてもう一点、「状態変化をどのObserverに通知するか(共通メソッドをどのオブジェクトに実行させるか)」ですが、新人の頃の自分なら、わざわざフラグ用の変数を作っていたように思います。

<?php
$objectA->isObserving = true;
$objectB->isObserving = false;
$objectC->isObserving = true;

$objects = [$objectA, $objectB, $objectC];

foreach ($objects as $object) {
    if ($object->isObserving) { $object->commonMethod(); }
}

でもそうじゃなく、Subject(メインルーチン)にObserver(メソッドを実行させるオブジェクト)を配列で持たせてやれば、それでいいんですよね。

<?php
$subject->observerList = [
    $ObjectA, $ObjectC
];
foreach ($subject->observerList as $object) {
    $object->commonMethod();
}

フラグ用の変数を作るのは、今後控えようと思います。フラグを立てたいオブジェクトを配列に持たせてやれば、それでよい!

結局、JavaScriptはどう継承すればいいのか

結局、JavaScriptはどう継承すればいいのか。
調べてもサイトによって書いてる事が違うので、ひとまず、信用できそうな方法を以下にまとめます。
(非 ES6 です。)


乃木坂46秋元真夏を親クラス、相楽伊織を子クラスとします。
2期生の相楽は、1期生秋元の意思を継いでるわけですね。


prototype継承用の関数。
一時的なプロキシコンストラクタを使います。

// prototype継承用の関数
var inherit = (function() {
    var F = function() {}; // クロージャを利用
    return function(C, P) {
        F.prototype = P.prototype;
        C.prototype = new F();
        C.uber = P.prototype;
        C.prototype.constructor = C;
    }
}());


こちら、親クラス。

// 親クラス
var Parent = function() {
    this.group = '乃木坂46';
    this.name = '秋元真夏';
}
Parent.prototype.shoot = function() {
    console.log(this.group + 'の' + this.name + 'です。ズッキュン!');
}


子クラスです。
applyメソッドで、親コンストラクタのプロパティを継承。
inherit関数で、親のprototypeに設定されたメソッドを継承してます。

// 子クラス
var Child = function() {
    Parent.apply(this, arguments); // 親のプロパティを継承
    this.name = '相楽伊織';
}
inherit(Child, Parent); // 親のメソッドを継承


実行。相楽伊織がズッキュンを継承してくれました。

var child = new Child;
child.shoot(); // 乃木坂46の相楽伊織です。ズッキュン!


うーん、まだprototypeを使いこなしてる感じがしないなぁ。
もう少し粘着して覚えていきます。

デコレータパターンでテンプレートメソッドを使う(PHP)

うーん、今までデコレータパターンを勘違いしてた。
デコレータパターンは継承と書き換え可能でした。


デコレータは動的に継承っぽい関係を作れるので、その点メリットがあります。


ただ、どの程度書き換え可能なのか…?


自分はテンプレートメソッドをよく使うんですが、そういうこともデコレータは出来るのか…?


というわけで、デコレータパターンでテンプレートメソッドを再現してみました。


ひとまず、一旦、普通の継承を。

<?php
class Super {
    public function render() {
        echo '<p>2</p>';
        $this->tmplMethod();
        echo '<p>4</p>';
    }
}
class Child extends Super {
    public function render() {
        echo '<p>1</p>';
        parent::render();
        echo '<p>5</p>';
    }
    public function tmplMethod() {
        echo '<p>3</p>';
    }
}

$obj = new Child();
$obj->render();

こいつを、デコレータで書き換える。


親となるクラス。

<?php
class Super {
    public function render(callable $callback) {
        echo '<p>2</p>';
        $callback();
        echo '<p>4</p>';
    }
}


子供のクラス。

<?php
class Child {
    public function __construct(Super $super) {
        $this->super = $super;
    }
    public function render() {
        echo '<p>1</p>';
        $this->super->render([$this, 'tmplMethod']);
        echo '<p>5</p>';
    }
    public function tmplMethod() {
        echo '<p>3</p>';
    }
}


実行。

<?php
$obj = new Child(new Super);
$obj->render();

 
 
 
うーん、引数でコールバックを渡すくらいしか、思いつかないなぁ。
ひとまず、この方法で使っていきます。

JavaScriptを超シンプルなMVCで書く(2)

以前の記事「JavaScriptを超シンプルなMVCで書く」を見返すとイマイチなので書き直し。
lonely-programmer.hatenablog.jp



以下挙動のアプリケーションを作ります。

・ボタンをクリックする毎に、白石麻衣の所属するグループが、乃木坂46 / 欅坂46 / SKE46 の順番でループする。



まずモデル。

var model = (function() {

    /* privateメンバ */
    var count = 0;
    var groups = [];
    // 現在のグループを取得
    var getNowGroup = function() {
      return groups[getIndex()];
    };
    // 現在のインデックスを取得
    var getIndex =  function() {
      // 0 → 1 → 2 → 0 → 1 → 2... とインデックスをループ
      return count % groups.length
    };

    /* publicメンバ */
    // グループを追加
    var addGroup =  function(group) {
      groups.push(group);
    };
    // グループを変更
    var changeHerGroup = function() {
      count++;
      $(this).trigger('groupIsChanged');
    };
    // グループ名を表示
    var getGroupName = function () {
      return getNowGroup().getName();
    };

    // publicメンバを開示
    return {
      addGroup: addGroup,
      changeHerGroup: changeHerGroup,
      getGroupName: getGroupName
    };

})();

モジュールパターンでオブジェクトを返却してます。
このオブジェクトはクロージャです。
privateメンバには外部からアクセスできません。



注目すべきはここ。

var changeHerGroup = function() {
  count++;
  $(this).trigger('groupIsChanged');
};

モデル内の値に変化があった場合、'groupIsChanged'イベントが発火されます。



オブジェクトではなく、コンストラクタ関数で返却する場合は、メソッド後半を以下のように書き変えます。

// コンストラクタ関数にpublicメンバを登録
var Constr = function() {
  this.addGroup = addGroup;
  this.changeHerGroup = changeHerGroup;
};
// プロトタイプにpublicメンバを登録
Constr.prototype = {
  getGroupName: getGroupName
};
// コンストラクタ関数を返却
return Constr;

 



グループ用のオブジェクトをモデルのプロパティに追加。
変数nameはprivateです。

// グループ用のコンストラクタ関数
function Group(name) {
  var name = name; // 書かなくても良い
  this.getName = function() {
    return name;
  };
}
// グループ追加
model.addGroup(new Group('乃木坂46'));
model.addGroup(new Group('欅坂46'));
model.addGroup(new Group('SKE48'));

 


ボタンがクリックされると、モデル内の値を更新。

// ボタンクリック時のイベントハンドラ
$('button').on('click', function() {
  model.changeHerGroup();
});

 


カスタムイベントを作成。

// レンダリング用カスタムイベント
$('span').on('render', function() {
  $(this).text(model.getGroupName());
});

 


モデル内の値に変化があれば、再レンダリングを行います。
jQueryのon()メソッドは、DOMオブジェクト以外も監視できることを最近知りました…。

// モデルを監視
$(model).on('groupIsChanged', function() {
  // テキストを再レンダリング
  $('span').trigger('render');
});

 


こちら、初期表示。

// テキストを初期表示
$('span').trigger('render');

// JavaScript処理完了後に画面表示
$('body').css('display', 'block');

 


ES6のことは一切考慮してません。笑
次回以降の記事で、ES6に書き換えてみようと思います。



以下、全コード。

<html>
<script src="jquery.js"></script>
<script>

$(function() {

  var model = (function() {

    /* privateメンバ */
    var count = 0;
    var groups = [];
    // 現在のグループを取得
    var getNowGroup = function() {
      return groups[getIndex()];
    };
    // 現在のインデックスを取得
    var getIndex =  function() {
      // 0 → 1 → 2 → 0 → 1 → 2... とインデックスをループ
      return count % groups.length
    };

    /* publicメンバ */
    // グループを追加
    var addGroup =  function(group) {
      groups.push(group);
    };
    // グループを変更
    var changeHerGroup = function() {
      count++;
      $(this).trigger('groupIsChanged');
    };
    // グループ名を表示
    var getGroupName = function () {
      return getNowGroup().getName();
    };

    // publicメンバを開示
    return {
      addGroup: addGroup,
      changeHerGroup: changeHerGroup,
      getGroupName: getGroupName
    };

  })();

  // Controller
  (function() {

    // グループ用のコンストラクタ関数
    function Group(name) {
      var name = name; // 書かなくても良い
      this.getName = function() {
        return name;
      };
    }
    // グループ追加
    model.addGroup(new Group('乃木坂46'));
    model.addGroup(new Group('欅坂46'));
    model.addGroup(new Group('SKE48'));

    // ボタンクリック時のイベントハンドラ
    $('button').on('click', function() {
      model.changeHerGroup();
    });

  })();

  // View
  (function() {

    // モデルを監視
    $(model).on('groupIsChanged', function() {
      // テキストを再レンダリング
      $('span').trigger('render');
    });

    // レンダリング用カスタムイベント
    $('span').on('render', function() {
        $(this).text(model.getGroupName());
    });

    // テキストを初期表示
    $('span').trigger('render');

    // JavaScript処理完了後に画面表示
    $('body').css('display', 'block');

  })();

});
</script>
<body style="display: none">
  <button>グループ変更</button>
  <p><span></span>の白石麻衣です。</p>
</body>
</html>

Riot.jsでモデルっぽいものを書いてみる

Riot.jsを初めて触ってみた。
あまりにシンプル過ぎて驚いた!
ほとんど覚えること無しに、それなりに書けそう。

というわけで、前回・前々回の流れで、簡単なサンプルをRiot.jsで書いてみます。

悩んだのが、Riot.jsにはモデルが見当たらない…?
おそらく、Observableがそれにあたるのか…?

とりあえず、モデルっぽいものを使ってみることにします。



HTMLはこれだけ。

<html>  
<body>
  <div id="nogi"></div>
  <div id="nogi-text"></div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/2.3.18/riot+compiler.js'></script>
</body>
</html>




モデルの発火、監視を可能にする。

<script>
var model = {
  isNogi: true
}
riot.observable(model);
</script>




これが、カスタムタグ。
HTMLタグと、JavaScriptとが合体してるイメージ。スタイルシートも定義できます。
「このタグはこういう挙動をする」ってのが見て一発で分かる。
わざわざソースコードを追いかけ回す必要もない。便利だなぁ…。

<script type='riot/tag'>  
<nogi>
  <button onclick='{ invert }'>{ name }</button>

  // モデルを更新
  this.invert = function () {
    model.isNogi = !model.isNogi;
    // 通知イベント発火
    model.trigger('hasInverted');
  };

  // 通知イベントを検知して、要素を描画
  model.on('hasInverted', (function () {
    this.render();
  }).bind(this));

  this.render = function () {
    this.name = model.isNogi ? '乃木坂46' : '欅坂46';
  };

  // 初期描画
  this.render();
</nogi>
</script>




こちら、別のカスタムタグ。
モデルの発火を検知して、再レンダリングする。

<nogi-text>
  <p>{ description }</p>

  // 通知イベントを検知して、要素を描画
  model.on('hasInverted', (function () {
    this.render();
  }).bind(this));

  this.render = function () {
    this.description = model.isNogi ? '2011年に結成されました。' : '2015年に結成されました。';
    this.update();
  };

  // 初期描画
  this.render();
</nogi-text>




マウントする。マウントって何?
カスタムタグと関連付けます。

<script>
  // マウント
  riot.mount('#nogi', 'nogi');
  riot.mount('#nogi-text', 'nogi-text');
</script>



覚えるのも書くのも簡単そうなので、せめて公式ドキュメントは全部読んでおこうと思います。
低コストで最新のJavaScriptフレームワークを身につけられる。お得ですなぁ…。

Backbone.jsでCollectionを触ってみる

Collectionを触ったことが無かったので、簡単に触っておこうと思います。

Cllectionは複数のModelを突っ込める器のようなものです。
DBに例えると、Modelがレコードだとすると、Collectionはテーブルになります。

以下、コードの全容。
タスクリストのようなアプリケーションです。
フォームにテキストを入力してボタンを押すと、リストにテキストが追加されます。
ただし、ブラウザの更新ボタンを押すとすべて初期化されます。笑

<html>
<script src="underscore-min.js"></script>
<script src="jquery.js"></script>
<script src="backbone-min.js"></script>
<script>
$(function() {
    // モデル
    var Model = Backbone.Model.extend();

    // コレクション
    var Collection = Backbone.Collection.extend({
        model: Model,
    });
    var collection = new Collection;

    // ビュー
    var View = Backbone.View.extend({
        el: 'body',
        events: {
            'click button': 'addItem',
        },
        initialize: function() {
            this.listenTo(this.collection, 'add', this.render);
        },
        addItem: function() {
            this.collection.add({ name: $('input').val() });
        },
        render: function(model) {
            $('ul').append($('<li>').text(model.get('name')));
            $('input').val('');
        },
    });
    var view = new View({ collection: collection });
});
</script>

<body>
    <input type="text">
    <button>追加</button>
    <ul>
    </ul>
</body>
</html>


var Collection = Backbone.Collection.extend({
    model: Model,
});
var collection = new Collection;

コレクションの作成。使用するモデルを指定します。

var View = Backbone.View.extend({
    el: 'body',
    events: {
        'click button': 'addItem',
    },
    initialize: function() {
        this.listenTo(this.collection, 'add', this.render);
    },
    addItem: function() {
        this.collection.add({ name: $('input').val() });
    },
    render: function(model) {
        $('ul').append($('<li>').text(model.get('name')));
        $('input').val('');
    },
});
var view = new View({ collection: collection });

ビューの作成。
コレクションにモデルが追加されると、コレクションで「add」イベントが発火されます。
ビューはaddイベントの発火を感知し、renderメソッドが実行されます。

render: function(model) {
    $('ul').append($('<li>').text(model.get('name')));
    $('input').val('');
},

このメソッドですが、自動で引数に追加されたモデルが渡されるようです。こりゃ便利だ。


Backbone.jsいいなぁ。でも古いフレームワークなので、新しいブログ記事が全く出てこない。
JavaScriptフレームワークの歴史を追うってことで、今後はReact.jsとかも触っていきたいです。

Backbone.jsで超シンプルなMVCを書く

前回はピュアなJavaScriptでシンプルなMVCを書くという趣旨でしたが…
フレームワークを使った方がシンプルに書けるのでは?」
という考えが巡り始めたので、今回は前回の内容をBackbone.jsで書き換えます。

Backbone.jsを簡単に説明すると…
日本語に訳して「背骨」なだけあって、MVCの枠組みだけを提供してくれます。
jQueryと共存できる…というか、Backbone.jsのライブラリ自体がjQueryに依存してます。
要は、大枠はBackbone.jsがやるから、細かいことはjQueryでやってくれってことです。

■ Model

var Model = Backbone.Model.extend({
    buttonStatus: true,
    changeData: function() {
        if (this.buttonStatus) {
            this.buttonStatus = false;
        } else {
            this.buttonStatus = true;
        }
        this.trigger('changingIsDone');
    }
});
var model = new Model;

まずモデル。データは全部モデルに持たせます。
モデルからDOM操作は行いません。

this.trigger('changingIsDone');

データに変更があれば、モデルの「changingIsDone」イベントが発火されます。
※HTMLの各要素から発火されるイベント(onclick, onchange等)とは別の概念と思われます。

■ View

var View = Backbone.View.extend({
    el: 'body',
    events: {
        'click button': 'switchButtonName',
    },
    initialize: function() {
        this.listenTo(this.model, 'changingIsDone', this.render);
        this.render();
    },
    switchButtonName: function() {
        this.model.changeData();
    },
    render: function() {
        var buttonName;
        if (this.model.buttonStatus) {
            buttonName = '乃木坂46';
        } else {
            buttonName = '欅坂46';
        }
        $('button').text(buttonName);
    },
});
var view = new View({ model: model });
events: {
    'click button': 'switchButtonName',
},

イベントハンドラを登録してます。
switchButtonNameメソッドから、モデルを操作してます。

initialize: function() {
    this.listenTo(this.model, 'chngingIsDone', this.render);
    this.render();
},

initializeメソッドはPHPの__construct()のようなものです。
listenToメソッドで、モデルの「chngingIsDone」イベントの監視を開始しました。
「chngingIsDone」イベントが発火すると、自動でrenderメソッドが実行されます。

render: function() {
    var buttonName;
    if (this.model.buttonStatus) {
        buttonName = '乃木坂46';
    } else {
        buttonName = '欅坂46';
    }
    $('button').text(buttonName);
},

renderメソッドではDOM操作を行っています。


ここで面白いのは、モデルの「changingIsDone」イベントを監視できるViewを複数追加できることです。

■ View 2

var SubView = Backbone.View.extend({
    el: 'p',
    initialize: function() {
        this.listenTo(this.model, 'changingIsDone', this.render);
        this.render();
    },
    render: function() {
        var description;
        if (this.model.buttonStatus) {
            description = '2011年に結成されました。';
        } else {
            description = '2015年に結成されました。';
        }
        $(this.el).text(description);
    },
});
var subView = new SubView({ model: model });

こちらのViewもモデルを監視しています。
つまり、モデルに変化があれば、View, SubViewの両方がレンダリングされる…ということです。

<body>
<button></button>
<p></p>
</body>

こちら、HTML。


ピュアなJavaScriptより、こちらの方がシンプルに書けました。
デメリットは、そのフレームワークの経験者でないと、システムの修正を気軽にできないことでしょうか…。