読者です 読者をやめる 読者になる 読者になる

メタプログラミングRuby その4 [学習メモ]

プログラミング 学習メモ Ruby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

3章はメソッドに関する話題で、この本の肝とも言える部分だ。
Ruby のような動的な言語は実行時にメソッドを定義することができる。
初めはこれらの利点はわかりにくい。
漠然と Ruby を使っているとまず使わない機能だろうし、それで不便だと思うないだろう。
ただこれらをうまく使うことでメソッドの定義そのものすら省略することができるようになり、
多くの API を持つ外部のプログラムとやりとりするような状況では非常に有用になる。

3.2 動的メソッド

メソッドはドット記法だけではなく、 send を使って呼び出すことができる。

str = "String"
str.upcase
# -> "STRING"

str.send(:upcase)
# -> "STRING"

だからどうしたという感じだが、こうすることで実行時にメソッドを決定できる。
おかしな例になるが、

str = "String"
methods = [:upcase, :downcase, :to_sym]

methods.each do |m|
  p str.send(m)
end
# -> "STR" "str" :stR

のようにできる。
Lisp とは違い Ruby では関数はオブジェクトではないのでイテレータに入れることができない。
そこでシンボルと send を通すことで任意の関数を呼び出せるようになる。

send でメソッドが動的に呼び出せるが、動的に定義することもできる。

class String
  define_method :my_method do
    "hello, #{self}!"
  end
end

str = "Bob"
str.my_method
# -> "hello, Bob!"

ここも肝はシンボルを使い任意の名前が渡せるところだ。

class DS
  def initialize # データソースに接続

  def get_name(id) # ...
  def get_age(id) # ...
  # ...
end
data_source = DS.new

class Person
  def initialize(id, data_source)
    @id = id
    @data_source = data_source
    data_source.methods.grep(/^get_(.*)$/) { Person.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) do
      @data_source.send "get_#{name}", @id
    end
  end
end

わかりにくいが data_source に存在する getter メソッドを呼び出すメソッドを動的に定義している。
こうすることで data_source に新たな getter メソッドが追加、あるいは削除されても Person クラスに変更を加える必要はなくなる。
目から鱗が落ちるようだが、慣れないうちはどの状況で使うべきか戸惑いそうだ。