動的dispatch
- method名が引数のため、実行時にmethodを決められる
- 動的に呼び出す
class MyClass
def my_method(my_arg)
my_arg * 2
end
end
obj = MyClass.new
obj.my_method(3)
# 6
obj.send(:my_method, 3)
# 6
動的method
- define_methodを使うとmethod実行時にmethod名を決められる
- 動的に定義する
class MyClass
define_method :my_method do |my_arg|
my_arg * 3
end
end
obj = MyClass.new
obj.my_method(2)
リファクタリング
original code
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
info = @data_source.get_mouse_info(@id)
price = @data_source.get_mouse_price(@id)
info = "Mouse: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
def cpu
info = @data_source.get_cpu_info(@id)
price = @data_source.get_cpu_price(@id)
info = "Mouse: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
def keyboard
info = @data_source.get_keyboard_info(@id)
price = @data_source.get_keyboard_price(@id)
info = "Mouse: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
動的dispatch適用
- sendを使ってmethodを動的に呼び出している
@data_source.send "get_#{name}_info" , @id
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
component :mouse
end
def cpu
component :cpu
end
def keyboard
component :keyboard
end
def component(name)
info = @data_source.send "get_#{name}_info" , @id
price = @data_source.send "get_#{name}_price", @id
info = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
動的methodを使う
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def self.define_component(name)
define_method(name) do
info = @data_source.send "get_#{name}_info" , @id
price = @data_source.send "get_#{name}_price", @id
info = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
define_component :mouse
define_component :cpu
define_component :keyboard
end
さらに
- String#grepにブロックを渡すと、正規表現にマッチした要素に対しブロックを実行する
- 正規表現にマッチした文字列は
$1
に格納される
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
end
def self.define_computer(name)
define_method(name) do
info = @data_source.send "get_#{name}_info", @id
price = @data_source.send "get_#{name}_price", @id
result = "#{name.capitalize}: #{info} (#{price})"
return "#{result}" if price >= 100
result
end
end
end
ゴーストメソッド
- 常にsuperを呼び出す
- 常にrespond_method_missing?を再定義する
- 本物のメソッドではない
- メソッド呼び出しを途中で捕まえているだけ
- 実際のメソッドとは振る舞いが異なる
- Object#methodの結果一覧に登場しない
- メソッド呼び出しが大量にあったり、呼び出すメソッドが実行時にしかわからない場合に使う
動的メソッド
- 通常のメソッド
- defの代わりに、define_methodで定義されたところが違うだけ
動的メソッドを使ったリファクタリング
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
end
def self.define_component(name)
define_method(name) do
info = @data_source.get_keyboard_info(@id)
price = @data_source.get_keyboard_price(@id)
result = "keyboard: #{info} ($#{price})"
price >= 100 ? "* #{result}" : result
end
end
end
Computer クラスを method_missing でリファクタリング
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def method_missing(name)
super if !@data_source.respond_to?("get_#{name}_info")
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} ($#{price})"
price >= 100 ? "* #{result}" : result
end
def respond_to_missing?(method, include_private = false)
@data_source.respond_to?("get_#{method}_info") || super
end
end