jQueryでMVVM
案件によっては、自由にJSフレームワークを使えないことがあるかもしれない。
既存システムがjQueryだけで作られているので、それを踏襲する必要があるかもしれない。
そんな時、jQueryでMVVM…双方向データバインディングが出来ると、作業が楽になる。
以下のページで、超便利なラッパークラスが紹介されてた!
var Observable = (function () { function Observable(obj) { if (obj == null) return; for (var p in obj) { Object.defineProperty(this, p, createDescriptor(p, obj[p])); } } function createDescriptor(key, value0) { var value = value0; return { get: function () { return value; }, set: function (v) { if (value === v) return; value = v; $(this).trigger("property_is_changed", [key, v]); }, enumerable: true, configurable: true }; } return Observable; })();
連想配列(JavaScriptで言うオブジェクト)でViewModelを作り、それを上記クラスでラッピングする。
<ul> <li><input data-model-key="num1"></li> <li><input data-model-key="num2"></li> <li><span data-model-key="total"></span></li> <li><span data-model-key="total2"></span></li> </ul>
var model = (function(){ var reload_view_is_finished; var model = { num1: 0, num2: 0, total: 0, reload_model: function(){ reload_view_is_finished = false; this.total = parseInt(this.num1) + parseInt(this.num2); }, reload_view: function(){ if (reload_view_is_finished === true) { return; } reload_view_is_finished = true; $('[data-model-key="num1"').val(this.num1); $('[data-model-key="num2"').val(this.num2); $('[data-model-key="total"').text(this.total); }, }; return model; }()); var observable_model = new Observable(model); $(observable_model).on('property_is_changed', function(){ this.reload_model(); this.reload_view(); });
observable_model のプロパティに値の変更があれば、property_is_changed イベントが発火される。
その結果、reload_model() と reload_view() が実行される。
$(function(){ // 初期表示 $(observable_model).trigger('property_is_changed'); $('input').on('change', function(){ var key = $(this).attr('data-model-key'); var value = $(this).val(); observable_model[key] = value; }); });
フィールドに入力があれば、自動で ViewModel に反映される。
反映された ViewMoel から property_is_changed イベントが発火されるので、reload_view() で画面に反映される。
これで、開発がだいぶ楽になってくれるはず…。
PHP 引数の多いメソッド対策
以下のように、同じような引数が羅列されているソースコードをよく見る。
<?php $this->getMemberAddress('秋元真夏', '乃木坂46', 'ソニーミュージック'); $this->getMemberAddress('白石麻衣', '乃木坂46', 'ソニーミュージック'); $this->getMemberAddress('山下美月', '乃木坂46', 'ソニーミュージック');
「乃木坂46、ソニーミュージック」
の部分が重複しており、大変見苦しい。
そういう時は、カリー化して、引数の数を減らしてやる。
<?php $curryingFunc = function($group, $company) { return function($name) use($group, $company) { return $this->getMemberAddress($name, $group, $company); }; } $getMemberAddressByName = $curryingFunc('乃木坂46', 'ソニーミュージック'); echo $getMemberAddressByName('秋元真夏'); // 東京都… echo $getMemberAddressByName('白石麻衣'); // 東京都… echo $getMemberAddressByName('山下美月'); // 東京都…
重複部分が無くなった。
以下のように、部分適用としても良い。
<?php $getMemberAddressByName = function($name) { return $this->getMemberAddress($name, '乃木坂46', 'ソニーミュージック'); }; echo $getMemberAddressByName('秋元真夏'); // 東京都… echo $getMemberAddressByName('白石麻衣'); // 東京都… echo $getMemberAddressByName('山下美月'); // 東京都…
引数が10個くらいあるメソッドを見てると、気が滅入ってくるよ。
PHP ユーザー定義関数に永続変数を持たせる
やりたいことは、JavaScriptで言うクロージャを作ること。
関数外のスコープにある変数を、関数内で保持していく。
useを使えば出来ると思ったら出来なかった。
useを使えるのは無名関数のみだったのか…。
<?php (function() { $cnt = 1; function increment() use(&$cnt) { // syntax error, unexpected 'use' echo $cnt; $cnt++; } })();
正解は、静的変数を使うこと。
<?php function increment() { static $cnt = 1; echo $cnt; $cnt++; } increment(); // 1 increment(); // 2 increment(); // 3 var_dump($cnt); // null
無名関数 + use(参照渡し)
= 関数定義 + 静的変数
= JavaScriptのクロージャ
お粗末さまでした。
if文を考える
以下のような if 文をよく見かける。
if has_white_skin && is_good_looking p 'まいやん' # 色白で美人なのが、まいやん elsif has_white_skin && is_cute p 'さゆにゃん' # 色白で可愛いのが、さゆにゃん end
ありがちだが、良くない。以下はもっと良くない。
if has_white_skin if is_good_looking p 'まいやん' # 色白で美人なのが、まいやん elsif is_cute p 'さゆにゃん' # 色白で可愛いのが、さゆにゃん end end
今後、スパゲティコードとして成長していくことが目に浮かぶ。
まず大切なのは、この処理には「白石麻衣」の場合と「井上小百合」の場合、その2通りの状態があること。
白石麻衣は「色白で美人」である。だが、どのような条件で「白石麻衣」と判断するか…それはどうでもいい。
大切なのは「白石麻衣」という状態が存在することだ。
状態を判断:色白で美人(どうでもいい) ↓ 状態:白石麻衣(大切) ↓ 処理:まいやん(ニックネーム。どうでもいい)
下の処理を見てみると、状態を判断(どうでもいい)と、処理(どうでもいい)だけしか書かれていない。
if has_white_skin && is_good_looking p 'まいやん' # 色白で美人なのが、まいやん
「状態が白石麻衣」であることは、どこにも書かれていない。
これを初めて読んだ人は「は?これ何について書かれているの?」となる。
以下の書き方が正解となる。
if has_white_skin && is_good_looking who = '白石麻衣' elsif has_white_skin && is_cute who = '井上小百合' end if who == '白石麻衣' p 'まいやん' elsif who == '井上小百合' p 'さゆにゃん' end
「状態の判断」と「処理」が分離された。
状態として、「白石麻衣」と「井上小百合」の2パターンが存在することが見て分かる。
JavaScript バブリング中毒
以下、セレクトボックスが3つ並んだHTML。
全てのセレクトボックスが選択完了した時、何か処理を実行したいとする。
<div id="wrapper"> <select id="select1"> <option value="">---</option> <option value="1">aaa</option> <option value="2">bbb</option> </select> <select id="select2"> <option value="">---</option> <option value="1">aaa</option> <option value="2">bbb</option> </select> <select id="select3"> <option value="">---</option> <option value="1">aaa</option> <option value="2">bbb</option> </select> </div>
昔の自分なら、こう書き始めていたと思う。
$('#select1').on('change', function() { }) $('#select2').on('change', function() { }) $('#select3').on('change', function() { })
だが、今日知ってしまった。
changeイベントだって、上位のDOMにバブリングすることを…。
$('#wrapper').on('change', function(e) { // 全て選択されたら、CSSを変更 if ($('#select1').val() && $('#select2').val() && $('#select3').val()) { $('#wrapper').css('background-color', '#0FF') } })
もう全部バブリングでええんちゃう…?
Ruby 元のクラスを触らずに機能を拡張する
機能を追加することになった。
だが、元のクラスは触りたくない。
だってバグりそうだから。前のテストのやり直しをさせらせるかもしれない…。
自分の身を守るためにも、安全に機能を拡張する方法をまとめる。
以下クラスを拡張する。
フルネーム「白石麻衣」と表示出来れば、OK。
class Family def name '白石' end end
1.継承する
定番。ぱっと見、分かりやすい。
インスタンス変数に依存した実装になりやすい。副作用大。
class Full < Family def name super + '麻衣' end end full = Full.new p full.name # 白石麻衣
2.委譲する
委譲先から委譲元のインスタンス変数を触れないので、疎結合。良い。
class Full def name family = Family.new family.name + '麻衣' end end full = Full.new p full.name # 白石麻衣
3.特異メソッドを定義
インスタンスにメソッドを定義する。
family = Family.new def family.full_name name + '麻衣' end p family.full_name # 白石麻衣
4.モジュールをextend
module Full def full_name name + '麻衣' end end family = Family.new family.extend Full p family.full_name # 白石麻衣
5.Decoratorパターン
委譲の発展版。
インスタンス変数を内側・外側で、それぞれどう持ち回すかは謎。工夫が必要っぽい。
class Full def initialize(family) @family = family end def name @family.name + '麻衣' end end full = Full.new(Family.new) p full.name # 白石麻衣
うーん、前任者のコードを触りたくない…。
Ruby クロージャを使ってインスタンス変数を減らす
インスタンス変数は悪である。
だって、クラス内のどこからでも、値を改変できるから。
そこで、インスタンス変数を減らす手立を考えてみた。
以下、ゲッターもどき。
構造体を返す。構造体はラムダをgetメンバに持っている。
ラムダはクロージャになる。変数immutable_varは保持される。
変数の中身(yield)は、後ほどブロックで渡す。
def make_getter immutable_var = yield struct = Struct.new(:get) struct.new( -> { immutable_var } ) end
以下、ゲッター兼セッターもどき。
getメンバ、setメンバを持つ。もちろんクロージャ。
def make_accessor immutable_var = yield struct = Struct.new(:get, :set) struct.new( -> { immutable_var }, -> new_var { immutable_var = new_var } ) end
以下で変数の中身を定義する。
'白石麻衣'、'平手友梨奈'となっているブロックの中身は、がっつり処理を書いてよい。
それら変数をまとめて、こちらも構造体で返す。
def make_immutables struct = Struct.new(:nogi, :keyaki) struct.new( make_getter { '白石麻衣' }, make_accessor { '平手友梨奈' }, ) end
コンストラクタで、上記構造体をインスタンス変数に入れる。インスタンス変数を使ってしまうが、これはしょうがないと思う。
def initialize @immutables = make_immutables end
変数を呼び出す。
呼び出し方が冗長な気もするが、憎きインスタンス変数を減らせたので、良しとする。
def execute p @immutables.nogi.get.call # 白石麻衣 p @immutables.keyaki.get.call # 平手友梨奈 @immutables.keyaki.set.call('長濱ねる') # 長濱ねるをセット p @immutables.keyaki.get.call #長濱ねる end
うーん、他に良い方法あるのかなぁ…。