elseif・switchは撲滅して連想配列
前回に引き続き、今回も同じテーマで。
以下、憎くて仕方ないelseif文。
id = 'nogi' if id == 'nogi' then name = '乃木坂46' elsif id == 'keyaki' then name = '欅坂46' else name = 'ソニーミュージック' end p name # 乃木坂46
本当にキモい。
たいした処理でもないのに、なぜ、こんなに行数を使うのか理解できない。
そこで、今日、良い記法を思いついた。
[ Ruby ]
id = :keyaki name = { nogi: '乃木坂46', keyaki: '欅坂46', }[id] || 'ソニーミュージック' p name # 欅坂46
存在しない id を指定すると、左辺が nil になる。
結果、右辺が実行される。短絡評価。
PHPで書くと、
<?php $id = 'keyaki'; $name = [ 'nogi' => '乃木坂46', 'keyaki' => '欅坂46', ][$id] ?? 'ソニーミュージック'; echo $name; // 欅坂46
PHP7の ??(Null合体演算子)を使うとスマート。
うーん、かなりイケてると思うんだが…。
elseifやswitch文は連想配列に置き換えてポリモーフィズム
elseifやswtich文は出来るだけ避けたい。
一連のスクリプトから状態を排除したいし、後々の改修で誰かがネストしまくるのが想像できる。
置き換えるには、Stateパターン等を使う必要があると思っていたが、実は連想配列で解決できることに気付いた。
以下、よく見るコード。Rubyで書く。
who = 'shizuka' if who == 'dora' then p '僕ドラえもん' elsif who == 'nobi' then p '僕のび太' elsif who == 'shizuka' then p '私しずか' end
とにかくキモい。このメソッド内で、dora / nobita / shizuka の分岐が、他に何度も繰り返されるのが目に浮かぶ。
これを連想配列にする。
hash = { dora: ->{ p '僕ドラえもん' }, nobi: ->{ p '僕のび太' }, shizuka: ->{ p '私しずか' }, } who = :nobi hash[who].call
ポリモーフィズムにもなっていて、結構イケてると思うんだけど…。
この、簡単に手を入れられない(深いネストを書けない)ところが良い。
実はelseif文は、連想配列に書き換えられる、という気付きでした。ちゃんちゃん。
Ruby 委譲
Rubyの便利な委譲な仕組み。
一つ目。Forwardableモジュール。
class Sony extend Forwardable def_delegators :@keyaki, :discord def initialize(keyaki) @keyaki = keyaki end end class Keyaki def discord print '僕は嫌だ!' end end sony = Sony.new(Keyaki.new) sony.discord # 僕は嫌だ!
def_delegatorsメソッドの引数に、委譲先のオブジェクト、委譲するメソッドを指定。
Sonyはdiscordメソッドを持っていないにもかかわらず、実行できている。
内部では、Keyakiオブジェクトが、discordを実行している。
二つ目。method_missing。
これは、PHPのマジックメソッド__call()と似たようなもの。
class Sony def initialize(keyaki) @keyaki = keyaki end def method_missing(name, *args) @keyaki.send name, *args end end class Keyaki def discord print '僕は嫌だ!' end end sony = Sony.new(Keyaki.new) sony.discord # 僕は嫌だ!
メソッド名を指定しなくていい分、こっちの方が楽かも。
何とかここらへんをうまく使って、Proxyパターンを楽に実装していきたい。
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()
うーん、ツリー構造自体、必要になる機会が無い。出番無いかも…。