2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

メタプログラミング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は使わない方がいいと思いました。
できるだけ動的メソッドを使って重複コードを無くした方がいいとおもいました

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?