[Ruby]ポケモンで理解する特異メソッド・特異クラスの旅

Ruby独特の概念に特異メソッド・特異クラスがあります。
この特異メソッド・特異クラスはRuby世界でのオブジェクトが独自の振る舞いをするために欠かすことができない概念です。

そこで今回は、ポケモンを例にしながら特異メソッド・特異クラスについて説明していきます！


## この記事の目的
特異メソッド・特異クラスについて理解する

## サンプルコード
今回は以下のサンプルコードで説明していきます。
```ruby:index.rb
class Pokemon
  attr_reader :name,
              :attack_point

  def initialize(name, attack_point)
    @name = name
    @attack_point = attack_point
  end

  def attack
    puts "#{@name}の通常攻撃！#{attack_point}のダメージを与えた！"
  end
end

class Pikachu < Pokemon
  def kaminari_attack
    puts "#{@name}のカミナリ！#{attack_point * 3}のダメージを与えた！"
  end
end

satoshi_no_pikachu = Pikachu.new('ピカ様', 50)

satoshi_no_pikachu.attack
satoshi_no_pikachu.kaminari_attack
# => ピカ様の通常攻撃！50のダメージを与えた！
# => ピカ様のカミナリ！150のダメージを与えた！
```
サンプルコードでは以下が実装されています。
1. `Pokemon`クラスとそのインスタンスメソッドである`attack`メソッド
2. `Pokemon`クラスを継承した`Pikachu`クラスとそのインスタンスメソッドである`kaminari_attack`メソッド
3. `Pikachu`クラスのオブジェクトであるピカ様

ピカ様は`Pikachu`クラスのオブジェクトなので`kaminari_attack`と`attack`の両方を使用できます。
このサンプルコードを使って、ピカ様に特異メソッドを追加していきましょう！


## 特異メソッドとはなんだ？
Rubyでは特定のオブジェクトにメソッドを追加することができます。
例えば、ピカ様だけに「なみのり」を覚えさせたいとします。
このとき、以下のように`オブジェクト.メソッド名`でメソッドを定義します。
```ruby
def satoshi_no_pikachu.naminori
  puts "#{@name}のなみのり！#{attack_point * 4}のダメージを与えた！"
end

satoshi_no_pikachu.naminori
# => ピカ様のなみのり！200のダメージを与えた！
```

もしくは`オブジェクト.define_singleton_method`で定義することもできます。この場合、引数がメソッド名、渡したブロック内がメソッドの処理になります。
```ruby
satoshi_no_pikachu.define_singleton_method :naminori do
  puts "#{@name}のなみのり！#{attack_point * 5}のすごいダメージを与えた！ついでに相手を麻痺状態にした！"
end
```

`naminori`メソッドはピカ様オブジェクトだけに追加されたメソッドです。
このように単一のオブジェクトに特化したメソッドを特異メソッドと呼びます。

特異メソッドはそのオブジェクト固有のメソッドのため、他のオブジェクトから呼び出すことはできません。
そのため、`Pikachu`クラスの別のオブジェクトが`naminori`メソッドを使用するとエラーになります。
```ruby:index.rb
normal_pikachu = Pikachu.new('普通のピカチュウ', 30)
normal_pikachu.naminori
# =>  undefined method `naminori' for #<Pikachu:0x00007fb3d6088658> (NoMethodError)
```

特異メソッドが定義されているかどうかは`singleton_methods`で確認できます。
```ruby
puts "普通のピカチュウの特異メソッド：#{normal_pikachu.singleton_methods}"
puts "ピカ様の特異メソッド：#{satoshi_no_pikachu.singleton_methods}"
# => 普通のピカチュウの特異メソッド：[]
# => ピカ様の特異メソッド：[:naminori]
```

特異メソッドを使用することで、同じ`Pikachu`クラスから生成されたピカ様と普通のピカチュウに、別の振る舞いをさせることができるようになります。

## クラスメソッドは、クラスの特異メソッドである
ここまではクラスから生成したオブジェクトに対して特異メソッドを定義しました。
では、クラス自身にも特異メソッドは定義できるのでしょうか？
実は、クラス自身に特異メソッドを定義することもできます。一般的にはクラスメソッドと呼ばれます。

なぜクラスメソッド=特異メソッドなのかについて、RUby世界のオブジェクトについて簡単に紹介しながら説明していきます！

### Ruby世界のオブジェクトとは何か？
クラスメソッドがクラスの特異メソッドであることを理解するためには、まずRuby世界のクラスもオブジェクトであることを理解する必要があります。

ではそもそも、オブジェクトとは何でしょうか？
Ruby世界のオブジェクトは
1. インスタンス変数を持つことができる
2. クラスから生成される
3. メソッドを使用できる

の3つの特徴があります。例えば、前述のピカ様で①~③を確認すると、以下のようになります。
ピカ様は`Pikachu`クラスから生成されたオブジェクトですね。
`@name`と`@attack_point`という二つのインスタンス変数を持っており、色々なメソッドも使うことができます。
```ruby
puts satoshi_no_pikachu.instance_variables
puts satoshi_no_pikachu.class
puts satoshi_no_pikachu.methods
# => @name
# => @attack_point
# => Pikachu
# => naminori kaminari_attack name attack_point attack instance_variable_defined? ...etc
```

ただ、実はオブジェクト自身にメソッドは定義されていません。
例えば、ピカ様が使用する`kaminari_attack`は`Pikachu`クラスのインスタンスメソッドですし、`attack`メソッドは`Pokemon`クラスのインスタンスメソッドです。
オブジェクト自身はメソッドを持っておらず、オブジェクトは自分が生成されたクラスやそのスーパークラスのメソッドを参照して呼び出しているわけですね。
図にすると以下のような感じです。

![段落テキスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576247/1f5aff36-ae68-e1e3-7051-b8ffa72de58d.png)

そのため、先ほど定義した`naminori`メソッドも実はピカ様オブジェクト自身に定義しているのではなく、別のところで定義されています。その別のところで定義した`naminori`メソッドを参照することでピカ様は波乗りを使うことができます。


### Ruby世界のクラスとは何か？
Ruby世界では「クラスはオブジェクト」です。そのためオブジェクトに当てはまる特徴はクラスにも当てはまります。例えば、`Pikachu`クラスは`Class`クラスのオブジェクトであることがわかります。
Ruby世界のすべてのクラス（`Pikachu`クラス、`Pokemon`クラス、`String`クラスなど）は`Class`クラスから生成されたオブジェクトです。
```ruby
puts Pikachu.instance_variables
puts Pikachu.class
puts Pikachu.methods
# => *Pikachuオブジェクトはインスタンス変数を持っていないため空で出力される
# => Class
# => allocate superclass new <=> ...etc
```

### クラスに特異メソッドを定義する
Ruby世界のすべてのクラスはオブジェクトです。
クラスはオブジェクトのため、先ほどピカ様オブジェクトに`naminori`メソッドを追加したように`Pikachu`クラスに特異メソッドを追加することができます。
追加方法は3通りです。

1. オブジェクト名＋メソッド名（`Pikachu.racial_value_hp`）
2. クラス内でself参照+メソッド名（`self.racial_value_attack`）
3. `class << self`内でメソッド名のみで定義（`racial_value_defense`）

```ruby:index.rb
class Pikachu < Pokemon
  def Pikachu.racial_value_hp
    puts 'ピカチュウのHP種族値は35'
  end

  def self.racial_value_attack
    puts 'ピカチュウのこうげき種族値は55'
  end

  class << self
    def racial_value_defense
      puts 'ピカチュウのぼうぎょ種族値は40'
    end
  end
  
  def kaminari_attack
    puts "#{@name}のカミナリ！#{attack_point * 3}のダメージを与えた！"
  end
end

Pikachu.racial_value_hp
Pikachu.racial_value_attack
Pikachu.racial_value_defense
# => ピカチュウのHP種族値は35
# => ピカチュウのこうげき種族値は55
# => ピカチュウのぼうぎょ種族値は40
```
上記で追加したメソッドは`Pikachu`クラス自身が呼び出せるメソッドであるため、一般的にクラスメソッドと呼ばれます。

クラスメソッドは、特定のオブジェクト（`Pikachu`クラス）にのみ定義されている特別なメソッドのため特異メソッドになります。
`Class`クラスから生成されているすべてのクラスのうち`Pikachu`クラスだけが`racial_value_hp`を使用できるので、特異メソッドになるということですね。


## 特異クラスとはなんだ？
では、これらの特異メソッドはどこに定義されているのでしょうか？

例えば、ピカ様に`naminori`メソッドを定義しましたがこのメソッドはどこにいるのでしょうか？
ピカ様オブジェクト自身にはいません。
なぜなら、オブジェクト自身はメソッドを持たないからです。

じゃあ`Pikachu`クラスでしょうか？
でも`Pikachu`クラスにいると仮定すると、普通のピカチュウオブジェクトも`naminori`メソッドを使えてしまいます。
それはまずい。。。

実は特異メソッドは特異クラスという場所にいます。特異クラスは特異メソッドを定義するための場所なのです。

特異クラスは`singleton_class`メソッドで確認できます。
```ruby
puts satoshi_no_pikachu.singleton_class
puts Pikachu.singleton_class
# => #<Class:#<Pikachu:0x00007fd8ce05d210>>
# => #<Class:Pikachu>
```
この特異クラスには以下の特徴があります。
1. インスタンスを一つしか持てない（そのため、シングルトンクラスとも呼ばれる）
2. 特異クラスにはオブジェクトの特異メソッドが定義されている

特異クラスは、そのオブジェクト固有の特異メソッドを定義する場所のため、必ず一つのオブジェクトとだけ紐付きます。

複数のオブジェクトと紐づいてしまうと、特異メソッドが複数のオブジェクトで使えてしまうからです。

では、特異クラスに特異メソッドが定義されていることを確認してみましょう。
確認するためには、特異クラスに`instance_methods`メソッドを使います。

```ruby
# ピカ様の特異クラスに、naminoriメソッドが定義されていることを確認
puts satoshi_no_pikachu.singleton_class.instance_methods(false)
# => naminori

# Pikachuクラスの特異クラスに、Pikachuクラスのクラスメソッドが定義されていることを確認
puts Pikachu.singleton_class.instance_methods(false)
# => racial_value_hp racial_value_attack racial_value_defense
```

やっと見つけましたね！
今まで定義してきた特異メソッドたちは、特異クラスのインスタンスメソッドとして定義されていました！

ピカ様は`naminori`を使用するときに、自身の特異クラスにインスタンスメソッドとして定義されている`naminori`メソッドを参照します。

これは、`kaminari`を使用するときに、自身を生成したクラスである`Pikachu`クラスのインスタンスメソッドを参照するのと同じですね。

同じように`Pikachu`クラスが`racial_value_hp`を使用するときは、自身の特異クラスにインスタンスメソッドとして定義されているインスタンスメソッドを参照して実行するわけですね。
そして、クラスに定義されたインスタンスメソッド（クラスから生成されたオブジェクトが使用できる）と区別する意味をこめて、クラス自身が実行できる特異メソッドをクラスメソッドと呼んでいるわけです。



## 特異メソッドの探索順序
最後に特異メソッドの探索順序について簡単に紹介します！

メソッドの探索順序をわかりやすく理解するために、以下のような出来事がポケモン世界で起きたとします。


ポケモン世界で革新が起こり、全てのポケモンが波乗りを使えるようになったとします。
その中でピカチュウたちは、波乗りに電流を乗せ、相手を必ず麻痺させることができるようになりました。
特にピカ様は一流の波乗り使いで、麻痺させるとともに威力を通常の5倍にすることができました。

上記のようなポケモン世界は以下のコードで実現できます。
```ruby:index.rb
class Pokemon
  attr_reader :name,
              :attack_point

  def initialize(name, attack_point)
    @name = name
    @attack_point = attack_point
  end

  def naminori
    puts "#{@name}のなみのり！#{attack_point}のダメージを与えた！"
  end
end

class Pikachu < Pokemon
  def naminori
    puts "#{@name}のなみのり！#{attack_point}のダメージを与えた！ついでに相手を麻痺状態にした！"
  end
end

satoshi_no_pikachu = Pikachu.new('ピカ様', 50)
normal_pikachu = Pikachu.new('普通のピカチュウ', 30)

def satoshi_no_pikachu.naminori
  puts "#{@name}のなみのり！#{attack_point * 5}のすんごいダメージを与えた！ついでに相手を麻痺状態にした！"
end

satoshi_no_pikachu.naminori
normal_pikachu.naminori
# => ピカ様のなみのり！250のすごいダメージを与えた！ついでに相手を麻痺状態にした！
# => 普通のピカチュウのなみのり！30のダメージを与えた！ついでに相手を麻痺状態にした！
```
普通のピカチュウは`Pikachu`クラスに実装されている`naminori`メソッドを実行しています
一方で、ピカ様は自身の特異クラスに定義されている`naminori`メソッドを実行しています。

これは以下のような流れで使うメソッドを探しているからです。

まず特異メソッドを定義していない普通のピカチュウオブジェクトは、自身を生成した`Pikachu`クラスから順番にクラスの継承をさかのぼって`naminori`メソッドを探します。
クラスの継承は`クラス.ancestors`で確認できます。
```ruby
puts Pikachu.ancestors
# => Pikachu Pokemon Object Kernel BasicObject
```
`Pikachu`クラス→`Pokemon`クラス→`Object`クラス→`Kernel`モジュール[^1]→`BasicObject`クラスの順番ですね。

一方でピカ様のように特異メソッドを定義すると、メソッドの探索順序は特異クラスが最優先になります。
`ピカ様の特異クラス`→`Pikachu`クラス→`Pokemon`クラス→`Object`クラス→`Kernel`モジュール→`BasicObject`クラスの順番ですね。

そのため普通のピカチュウとピカ様はそれぞれ別の`naminori`メソッドを使用することができます。

また、もし仮に特異クラスと`Pikachu`クラスのどちらにも`naminori`メソッドがない場合、`Pokemon`クラスの`naminori`メソッドが実行されます。

## まとめ
- 特異メソッドはオブジェクト固有のメソッドを定義する
- 特異クラスは特異メソッドを定義している場所
- メソッドの探索順序はまず特異クラスが最優先。その後クラスの継承をさかのぼっていく

## 参考書籍
[メタプログラミングRuby第２版](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 "メタプログラミングRuby第２版")

[^1]:`Kernel`モジュールは`Object`クラスに含まれているモジュールのため、厳密に言えばクラスの継承ではありません。