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 はメソッド呼び出しのデリゲートに使えそうです.