Meta-3章-メソッド


![](https://ruby.tw/logo.gif)

## 動的dispatch
* method名が引数のため、実行時にmethodを決められる
* 動的に呼び出す

```ruby
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名を決められる
* 動的に定義する

```ruby
class MyClass
	define_method :my_method do |my_arg|
		my_arg * 3
	end
end

obj = MyClass.new
obj.my_method(2)
```

## リファクタリング
### original code

```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)
		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`

```ruby
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を使う

```ruby
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`に格納される

```ruby
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
```