JQ Blog

メソッド

動的メソッド

メソッドを呼び出す方法

  • ドットを使う
  • send()を使う
    • send()メソッドの第一引数は呼び出すメソッド名 => 文字列またはシンボル
    • その他の引数はそのままメソッドに渡される

send()を使えばコードの実行時に呼び出すメソッドを直前に決められる。なので動的な処理が可能になる。これを動的ディスバッチと呼ぶ。

プライバシー問題

send()メソッドの問題はprivateメソッドも呼ばれるということだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class C
  private

  def private_method
    puts "called private method"
  end
end

C.new.private_method
=> NoMethodError: private method 'private_method' called for #<C:0x007f8149e5c8e8>

C.new.send(:private_method)
"called private method"
=> nil

プライバシーの尊重のため、public_send()を使う方法もある。

  • 例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class C
  def public_method
    puts "called public method"
  end

  private

  def private_method
    puts "called private method"
  end
end

C.new.public_send(:public_method)
"called public method"
=> nil

C.new.public_send(:private_method)
=> NoMethodError: private method 'private_method' called for #<C:0x007f8149e5c8e8>

メソッドを動的に定義

  • define_method()

Module#define_method()を使えば、メソッドをその場で定義できる。

1
2
3
4
5
6
7
8
9
class C
  define_method :my_method do |my_arg|
    my_arg * 3
  end
end

obj = C.new
obj.my_method(2)
=> 6

define_methodはClassの中で実行され、my_method()Cクラスのインスタンスメソッドとして定義される。実行時にメソッドを定義するこの技術を動的メソッドと呼ぶ。

method_missing()

method_missing()のオーバーライド

method_missing()メソッドはBasicObjectのインスタンスメソッドだ。つまり、BasicObjectを継承しているObjectを継承するオブジェクトはmethod_missing()を持っているということになり、もちろんオーバーライドもでき、存在しないメソッドに対するハンドリングもできるということだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class C
  def method_missing(method, *args)
    puts "#{method}(#{args.join(',')})を呼びました"
    puts "(ブロックを渡した)" if block_given?
  end
end

C.new.hello('a', 'b')
hello(a,b)を呼びました
=> nil

C.new.hello('a', 'b') do
  # block
end
hello(a,b)を呼びました
(ブロックを渡した)
=> nil

ゴーストメソッド

同じようなメソッドをたくさん定義しなければならないとき、いちいち定義するより、method_missing()を呼び出す方がもっと楽かもしれない。method_missing()での処理は呼び出し側からは通常の呼び出しのように見える。しかし、レシーバに対応するメソッドは見当たらない。これはゴーストメソッドと呼ぶ。上記の例ではhello()がゴーストメソッドなるのだ。

もっとmethod_missing()

メソッド名が衝突したら

1
2
3
4
5
6
7
8
9
10
11
class C
  def method_missing(method, *args)
    puts "#{method}(#{args.join(',')})を呼びました"
    puts "(ブロックを渡した)" if block_given?
  end
end

C.new.display('a', 'b') do
  # block
end
ArgumentError: wrong number of arguments (given 2, expected 0..1)

上記のコードでは

1
2
display(a,b)を呼びました
(ブロックを渡した)

とプリントされると予想したけど、プリントされずArgumentErrorになってしまった。
これの問題はObjectから継承されたメソッドの中でdisplayというメソッドがあり、method_missing()が呼ばれなかったからだ。
これはは必要ないメソッドを全部削除することで対応できる。メソッドをすべて削除することをブランクスレートと呼ぶ。

  • 例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class C
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /__|method_missing/
  end

  def method_missing(method, *args)
    puts "#{method}(#{args.join(',')})を呼びました"
    puts "(ブロックを渡した)" if block_given?
  end
end

C.new.display('a', 'b') do
  # block
end
display(a,b)を呼びました
(ブロックを渡した)
=> nil

このコードは予想通りmethod_missing()を呼び出すことができる。

まとめ

  • メソッドはsend()でも呼び出せる
  • send()privateメソッドも呼び出せるため、public_send()を使うこともできる
  • define_method()でメソッドを動的に定義できる
  • method_missing()を利用すると存在しないメソッド、ゴーストメソッドに対するハンドリングや処理ができる