メタプログラミングRuby第2版 / 第3章メソッド[勉強メモ]


メタプログラミングRubyを読んでいます。完全自分用のメモ記事です。
第3章で特に参考になった部分を中心に書いています。


🗽 3章メソッド

動的言語Rubyと静的言語

  • JavaとかCはコンパイラがメソッドのチェックを行う
  • Rubyは実行する際にメソッドを探す。な。ればエラーになる

Rubyの動的言語の特徴を活かす

Rubyの動的言語の特徴を利用して、Rubyは重複を排除するために、次の様な方法を取ることができる。

- 動的にメソッドを定義
- method_missingを使う

動的メソッドを使ったリファクタリング

まずは修正前の変更。

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)
result = "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)
result = "Cpu: #{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)
result = "keyboard: #{info} ($#{price})"
return "* #{result}" if price >= 100
result
end
end

動的メソッドを使ってリファクタリングを行った結果。

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

method_missingを使ったリファクタリング

さきほどの 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

ただし、 method_missing はバグが発生した場合に気付きにくい特徴があるので利用には注意が必要。
あと、method_missing よりも、respond_to_missing? のほうが適切というお話もあります。

Always Define respond_to_missing? When Overriding method_missing

ゴーストメソッドの最大の問題点は、本物のメソッドではないという点だ。
振る舞いはメソッドなのに、 Object#methods に含まれないのだ。

🐰 Pryのコード

def refresh(options={})
defaults = {}
attributes = [ :input, :output, :commands, :print, :quiet,
:exception_handler, :hooks, :custom_completions,
:prompt, :memory_size, :extra_sticky_locals ]
attributes.each do |attribute|
defaults[attribute] = Pry.send attribute
end
# ...
defaults.merge!(options).each do |key, value|
send("#{key}=", value) if respond_to?("#{key}=")
end
true
end
Excerpt From: Paolo Perrotta. “メタプログラミングRuby 第2版.” iBooks.

🍣 サンプルソース

何かの役に立つこともあるかもですので、リポジトリも公開しておきます。

morizyun/meta_programming_ruby2 - GitHub

📚 おすすめの書籍