動的メソッドの解説

メタプログラミングRuby3章を読んでそれをまとめてみようとおもいます。

タイトルにある動的メソッドとは何なのでしょうか？

## 動的にメソッドを呼び出す
Rubyではメソッドを呼びだすときはドットを使って呼び出しています。
```array.count```のようにオブジェクトにたいしてメソッドを呼び出しています。
そのほかにsendメソッドを使う方法もあります。以下のような使い方をします
```
class Example
  def ex_method(arg)
    arg * 3
  end
end

obj = Example.new
obj.ex_method(30) #=> 90
obj.send(:ex_method, 30) #=> 90
```
同じex_methodを呼び出してはいますが、コードを実行する際に呼び出すメソッドを決められるのがsendメソッドのポイントです。
このように、コードの実行時に呼び出すメソッドを決められることを**動的ディスパッチ**といいます。

このようにメソッドを動的に呼び出すことができることを紹介しましたが、メソッドを動的に**定義すること**も可能なんです。

## 動的にメソッドを定義する
動的にメソッドを定義するために使われるメソッドは、```define_method```です。
このメソッドを使えば、実行時にメソッド名を決めれるんです。これを**動的メソッド**といいます
動的ディスパッチと動的メソッドを使って下のコードをリファクタしてみましょう。

※以下のコードは参考資料に書いてある『メタプログラミングRuby』から引用しているコードです。
```
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
```

上記のコードだとハードウェアが増えれば増えるほど、メソッドが必要になってしまうので、保守性が保てなくなってしまいますね。
この記事で、```def```以外に```define_method```でメソッドを定義する方法を紹介しました。
また、メソッドを呼び出す方法として、ドットを使う以外に```send```メソッドを使ってメソッドを呼び出す方法を紹介しました。
これらを使って、リファクタリングしてみましょう。重複したコードを抽出してメッセージを出力するメソッドにまとめてみます。
```
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
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
end
```

新たにcomponentメソッドを用意してそこで、メッセージを出力するコードを記述している。だいぶスッキリしたような感じになりましたね。
でも、```def```キーワードが似たような使われ方をしていますね。そこをリファクタしてみましょう。
```
class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  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

  define_component :mouse
  define_component :cpu
  define_component :keyboard
end
```
Computerクラスにdefine_componentを呼び出したいのでクラスメソッドを指定しています。

## method_missing
method_missingとは継承チェーンをたどってメソッドが見つからなかった際に最後に呼び出されるメソッドです。いうならばエラー文ですね。NomethodErrorが有名ですね。
そしてこのメソッドは、BasicObjectクラスに定義されているので、どこからでも呼び出すことができるんですね。
本書では、method_missingを使ってリファクタもされていますが、このメソッドは使い方を間違うとメンドクサイことになります
例えば以下のコードを見てください
※以下のコードは参考資料に書いてある『メタプログラミングRuby』から引用しているコードです。
```
class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    3.times do
      number = rand(10) + 1
      puts "#{number}..."
    end
    "#{person} got a #{number}"
  end
end

number_of = Roulette.new
puts number_of.bob
puts number_of.frank 
# => 数字がズラズラ...
...
1...
9...
10...
7...
SystemStackError (stack level too deep)
```
このようにSystemStackErrorと出てしまい、どこに問題があるかが分からなくなってしまうのがこのメソッドの怖いところです。
これが大規模のシステムになったらメチャクチャめんどくさいことになります。
この章を読んで思ったのは、できるだけ```method_missing```は使わない方がいいと思いました。
できるだけ動的メソッドを使って重複コードを無くした方がいいとおもいました

以上です。何か間違いがございましたら、ご教示いただけますと幸いです。
【参考資料】

https://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437