Ruby メタプログラミング入門(1) 動的なメソッドディスパッチ
メソッドが存在しない場合に必ず呼ばれるメソッド,method_missing をオーバーライドすることにより実現します.
#!/usr/bin/env ruby # -*- coding: utf-8 -*- # 1度の試行につき4回実行されるルーレット.ただし,前回の値より小さい値しか出ない. class Roulette Max = 100 def initialize @min = Max + 1 end # 既存のメソッドを削除することで既存のメソッド名と衝突した場合でも正しく動作させる # もし既存のメソッドと衝突した場合,既存のメソッドが勝つ # なぜなら method_missing はメソッド探索の最後に呼ばれるから. instance_methods.each do |m| undef_method m unless m.to_s =~ /^__|object_id|method_missing|respond_to\?/ end # 存在しないメソッド呼び出しがあった時に,そのメソッドがあるかのように見せる def method_missing name, *args name = name.to_s.capitalize puts "#{name}'s roulette!" num = Max 3.times do num = Random.rand 1..num puts "#{num}..." sleep 1 end num = Random.rand 1..num if num==@min if Random.rand(2) == 0 num += 1 else num -= 1 end elsif num<@min @min = num end puts "#{num}!!","" num end # あらゆる名前のメソッドがあるかのように振る舞う def respond_to? method true end end # # main # if __FILE__ == $0 then roulette = Roulette.new # 本来 linda_pp というメソッドは存在しないため,method_missing が呼ばれ処理される. # 外からはあたかもメソッドがあるかのようにみえる l = roulette.linda_pp i = roulette.inu # 値が小さかったほうが負け! puts (l>i ? "Linda_pp" : "犬") + " won!" end
Linda_pp's roulette! 35... 28... 28... 15!! Inu's roulette! 32... 27... 25... 18!! 犬 won!
今回はあらゆる名前でメソッドが呼ばれる可能性があるので method_missing を使いましたが,予め呼ばれるメソッドが分かっている場合は define_method で動的にメソッドを定義し,sendで呼び出す方法もあります.
class Greeting # "greeting_hoge" というメソッドをメンバー分定義 %w[ Alice Bob Tom Yumi ].each do |name| define_method("greeting_#{name}") do puts "Hello, #{name}" end end end # # main # if __FILE__ == $0 then greeting = Greeting.new # 誰か一人にランダムに挨拶する greeting.send "greeting_#{%w[ Alice Bob Tom Yumi ].sample}" end
define_method は複数のメソッドの一括定義に,method_missing はメソッド呼び出しのデリゲートに使えそうです.