Rubyでクロージャ
JavaScriptでよく見るクロージャを、Rubyでもやってみる。
JavaScript 関数スコープ
・内側から外側の変数は利用できる。
・外側から内側の変数は利用できない。
JavaScriptの場合、関数スコープを利用して、クロージャを実装する。
Rubyの場合、ブロックスコープを利用して、クロージャを実装する。
Procオブジェクトを返す関数を定義する場合。
def createCounter num = 0 Proc.new do num += 1 p num end end counter = createCounter counter.call # 1 counter.call # 2 counter.call # 3 counter.call # 4 counter.call # 5
ProcオブジェクトからProcオブジェクトを返す場合。
createCounter = Proc.new do num = 0 Proc.new do num += 1 p num end end counter = createCounter.call counter.call # 1 counter.call # 2 counter.call # 3 counter.call # 4 counter.call # 5
クロージャをあまり実務で使ったことない。もっと積極的に使おう。
Ruby Procを触る
引数にブロックを取る関数。
def doSomething(something, &toDo) toDo.call(something) end
PHP = 無名関数
Ruby = ブロック
という考え方で良いと思う。
以下、do から end がブロック。
Rubyでは、ブロックも引数の一種に考えるイメージだろうか。
doSomething 'メロン' do |food| p food << 'を食べる' end
メロンを食べる
ではそのブロック(無名関数)を変数に入れたい場合はどうするか。
Procオブジェクトとしてラッピングする必要がある。
def doSomething(something, toDo) toDo.call(something) end eatFood = Proc.new do |food| p food << 'を食べる' end doSomething 'リンゴ', eatFood
リンゴを食べる
こっちは、doSomethingの引数toDoに「&」が無い。
# ブロックを渡す場合 def doSomething(something, &toDo) # Procオブジェクトを渡す場合 def doSomething(something, toDo)
この「&」は、普通のブロックをProcオブジェクトに変換している…らしい。
Procオブジェクト。まだよく分からないが、ここらへんを理解できると、後々楽になりそうな気がする。
PHP Proxyパターンを楽に実装する
Proxyパターンは有用である。最近知ったから使ったことないけど。
例えば、以下、RobotクラスとMissileクラス。
ロボットクラスは、ミサイルの発射をミサイルクラスに委譲している。
<?php class Robot { function __construct() { $this->missile = new Missile(); } function launchMissile() { $this->missile->fire(); } } class Missile { public function fire() { echo '発射!'; } } $robot = new Robot(); $robot->launchMissile();
発射!
しかし、ただ発射するだけでは地味なので、おっぱいからミサイルを発射するように改造したとする。
<?php class Robot { function __construct() { $oppai = new Oppai(); $oppai->missile = new Missile(); $this->missile = $oppai; } function launchMissile() { $this->missile->fire(); } } class Oppai { public function fire() { echo 'おっぱいミサイル'; $this->missile->fire(); } }
おっぱいミサイル発射!
Missileクラスを一切触っていない。
実行元なる Robot::launchMissile にも一切手を加えていない。
このように、共通のインターフェイスを持つ中間層のクラスを差し込むことで、色々と応用を利かせることが出来る。
ただ、問題点がある。
Missileクラスが100個メソッドを持っていれば、Oppaiクラスにも同名のメソッドを100個用意する必要がある。
でも大丈夫。
<?php class Oppai { public function __call($name, $args) { echo '超おっぱいミサイル'; $this->missile->{$name}($args); } }
超おっぱいミサイル発射!
PHPには、__call() というマジックメソッド(と呼ばれる特殊なメソッド群)が存在する。
存在しないメソッド名が呼ばれると、この __call() が実行される。
これで、面倒なProxyパターンも気軽に使っていける。
JavaScript ツリー構造はCompositeパターン
ツリー構造といえばあれだ。
営業部
┗ 1課
┗ 山田
┗ 鈴木
┗ 2課
┗ 佐藤
みたいになってるやつ。
デザインパターンには、このツリー構造をうまく処理できる「Compositeパターン」というものがある。
だが、ツリー構造自体、扱う機会が特に無かったので、Compositeパターンの存在を忘れていた。
「営業部」のようなグループは、中にさらにグループ(1課とか)、あるいは個人(山田とか)を格納できる。
しかし、個人はそれ以上何も格納できない。
以下、グループ・個人共通のインターフェイス。
function Component(name) { this.children = [] this.name = name } Component.prototype.add = function (child) { this.children.push(child) return this // メソッドチェーン用 } Component.prototype.showName = function (indent) { var indent = indent || '' console.log(indent + this.name) indent = indent + ' ' // 子要素の表示はインデントを追加 for (var i = 0; i < this.children.length; i++) { this.children[i].showName(indent) } }
以下、グループを表すクラス。Componentを継承している。
function Composite(name) { Component.apply(this, arguments) } Composite.prototype = Object.create(Component.prototype) Composite.prototype.constructor = Composite
以下、個人を表すクラス。同じく、Componentを継承している。
addメソッドは、利用不可になるようオーバーライドしている。
function Leaf() { Component.apply(this, arguments) } Leaf.prototype = Object.create(Component.prototype) Leaf.prototype.constructor = Leaf Leaf.prototype.add = null
ツリー構造を作成して表示。
;(new Component('Sony Music')).add( (new Component('乃木坂46')).add( new Leaf('白石麻衣') ).add( new Leaf('西野七瀬') ) ).add( (new Component('欅坂46')).add( new Leaf('平手友梨奈') ).add( new Leaf('長濱ねる') ) ).showName()
うーん、ツリー構造自体、必要になる機会が無い。出番無いかも…。
JavaScript Decoratorパターン
Decoratorパターンの良いところは、
主体となるオブジェクトと、処理を委譲しているオブジェクトの間に、スッと割り込ませることが出来ること。
そして、その処理を委譲しているオブジェクトには、傷をつけなくて済む。
Decoratorでラッピングするか否か、動的に決めることも出来る。
処理を付け足したり、付け足さなかったり…そんな時に使えるかも。使いたい。けど使いにくい。
function Component() { this.showNumber = function () { console.log(2) } } function Decorator(component) { this.component = component this.showNumber = function () { console.log(1) this.component.showNumber() console.log(3) } } var decorator = new Decorator(new Component()) decorator.showNumber()
1
2
3
Componentの処理のど真ん中に、Decoratorからの処理を埋め込みたい…そんな時もあるかもしれない。
function Component() { this.showNumber = function () { console.log(2) this.decorator.showInnerNumber() console.log(4) } } function Decorator(component) { this.component = component this.component.decorator = this this.showNumber = function () { console.log(1) this.component.showNumber() console.log(5) } this.showInnerNumber = function () { console.log(3) } } var decorator = new Decorator(new Component()) decorator.showNumber()
1
2
3
4
5
DecoratorはComponentを持ち、ComponentもDecoratorを持つ。お互いに委譲し合っている。
イマイチかもしれない。急遽、応急処置が必要な時に使おう。
ループの中のif文が現れたら、Observerパターンを検討する
やはり自分はObserverパターンを理解していなかった。
Observerパターンは、多種多様な輩をループで回す時、真価を発揮する。
以下、よく見るケース。JavaScriptで。
function Student(name, sex) { this.name = name this.sex = sex } var students = [ new Student('野比のび太', 'boy'), new Student('源静香', 'girl'), new Student('剛田武', 'boy'), new Student('骨川スネ夫', 'boy') ] for (var i = 0; i < students.length; i++) { if (students[i].sex === 'boy') { console.log('ムフフ、女子の着替え覗いちゃおうかなぁ。') } else if (students[i].sex === 'girl') { console.log('変態男子!ケダモノ!') } }
ムフフ、女子の着替え覗いちゃおうかなぁ。
変態男子!ケダモノ!
ムフフ、女子の着替え覗いちゃおうかなぁ。
ムフフ、女子の着替え覗いちゃおうかなぁ。
ループの中にif文。分岐が多いのは良くない。
これがネストしたり、else if が何個も続くと、見てるだけで気分が悪くなってくる。
こういう時は、Observerパターンを使う。
function Boy() { Student.apply(this, arguments) } Boy.prototype.say = function () { console.log('ムフフ、女子の着替え覗いちゃおうかなぁ。') } function Girl() { Student.apply(this, arguments) } Girl.prototype.say = function () { console.log('変態男子!ケダモノ!') } var students = [ new Boy('野比のび太', 'boy'), new Girl('源静香', 'girl'), new Boy('剛田武', 'boy'), new Boy('骨川スネ夫', 'boy') ] for (var i = 0; i < students.length; i++) { students[i].say() }
ムフフ、女子の着替え覗いちゃおうかなぁ。
変態男子!ケダモノ!
ムフフ、女子の着替え覗いちゃおうかなぁ。
ムフフ、女子の着替え覗いちゃおうかなぁ。
最後のfor文からifが消えた。
Observerパターンが実はとんでもない汎用性があることに今さら気付いた。
Javascript 関数のデフォルト値設定の罠
今まで何気なくやってた、関数のデフォルト値設定。
function foo(a) { var a = a || 1; ... }
実は愚かな行動だったと知った。
JavaScriptの「&&」「||」について盛大に勘違いをしていた件 - Qiita
例えば、
foo(0); // 1
引数が 0 や false の場合、意図どおりに動作しない。
以下のようにしてやればOK。
// 1 typeof a === 'undefined' && (a = 1) // 2 if (typeof a === 'undefined') a = 1; // 3 a = typeof a !== 'undefined' ? a : 1;
実はJavascript、「短絡評価」をできる、数少ない過激な言語だったと知った。
そこで、すべてのif文は、and or に書き換えられるのでは…、と危険な思想が頭をよぎった。
例えば、
if (a === 1) { console.log(1); } else if (a === 2) { console.log(2); } else { console.log('unknown'); }
個人的には、else if とか大っ嫌い。
以下のように書き換えられるか?
a === 1 && console.log(1) || a === 2 && console.log(2) || console.log('unknown')
うーん、console.log()の関数実行部分が、true を返すか、false を返すかで、挙動が変わってしまう。
いまいちだが、以下のような関数を定義した。
function t() { return true; } function f() { return false; }
&& に続く関数は、絶対に true としたい。
|| に続く関数は、絶対に false としたい。
a === 1 && t(console.log(1)) || a === 2 && t(console.log(2)) || console.log('unknown')
一応、意図した動作になってるっぽい。
独自関数をグローバルに定義してるのはキモいので、何とかするとして、
この Javascript の過激な仕様に感激した。
もともとイカれた(改め、クセの強い)言語だと思っていたが、そこを遥か超えてきやがった。すげぇ。