特異メソッド、特異クラスとクラスメソッドの関係について

はじめに

昨晩、rubyの特異メソッドや特異クラスについて調べたところ深みにはまってしまい、気付いたらAM2:00になっていました。。
せっかくなので、調べた結果をまとめてみました。

特異メソッドとは何か?

一言でいうと、あるオブジェクト固有のメソッドです。
特異メソッドはdef オブジェクト名.メソッド名という記法で定義します。

class People
  def hello
    puts "hello!"
  end
end
tanaka = People.new

# tanakaに特異メソッドbyeを定義
def tanaka.bye
  puts "goodbye!"
end

tanaka.bye
# => goodbye!

# 別のオブジェクトからはbyeメソッドは呼び出せない
suzuki = People.new
suzuki.bye
# => NoMethodError: undefined method `bye' for #<People:0x007fc328da83b8>

特異メソッドはクラスではなくオブジェクトに直接定義されるメソッドになります。そのため、特異メソッドbyeはオブジェクトtanakaからのみ呼び出すことができ、同じPeopleクラスに属するsuzukiからは呼び出すことができません。

クラスメソッドとは何か?

クラスメソッドは、クラスに属するメソッドであり、クラスから直接呼び出すことができるメソッドであるというのが、通常の理解かと思います。
では、なぜクラスから直接呼び出すことができるのでしょうか?
その答えは、クラスメソッドがClassクラスのオブジェクトの特異メソッドだからです。
Classクラスとは、簡単にいうと全てのクラスの元となるクラスです。例えば、クラスの基本的なメソッドであるnewメソッドはClassクラスのインスタンスメソッドとして定義されています。
分かりにくいので、詳しく見ていきます。

class People
  # インスタンスメソッド定義
  def hello
    puts "hello!"
  end
  # 特異メソッド(クラスメソッド)定義
  def self.bye
    puts "goodbye!"
  end
end

# クラスメソッドは'クラス名.メソッド名'で呼び出すことができる
People.bye
# => goodbye!

# インスタンスメソッドhelloは、インスタンスからしか呼び出せない
People.hello
# => NoMethodError: undefined method `hello' for People:Class
tanaka = People.new
tanaka.hello
# => hello!

クラスメソッドは、クラス定義の中でdef self.メソッド名と定義します。このdef self.メソッド名selfとは、ClassクラスのオブジェクトであるPeopleです。
実際に確認してみます。

class People
  # selfの中身を確認
  p self
  # selfのクラスを確認
  p self.class
end
# => People
# => Class

よく言われているように、rubyでは全てがオブジェクトです。そのため、Peopleクラスもクラスであると同時にオブジェクトでもあります。
前述のクラスメソッドの定義で使用されていたdef self.メソッド名という記法ですが、selfはオブジェクトPeopleですので、def People.メソッド名と同じ意味になります。
これは、特異メソッドの定義方法であるdef オブジェクト名.メソッド名と同一です。特異メソッドはオブジェクトに直接定義されるメソッドですので、オブジェクト(ここではPeopleクラス)から呼び出すことができます。
その結果、People.メソッド名という記法で呼び出すため、クラスから直接呼び出しているように見えるということです。

  • Classクラス
  • Peopleクラス
  • Peopleクラスのオブジェクトtanaka

の関係性は下記図になります。

スクリーンショット 2017-03-07 12.45.15.png

特異クラスとは何か?

さて、特異メソッドとクラスメソッドの関係は上記の通りですが、実は特異メソッドの説明の中で、正確ではない表現になっていた箇所があります。
それは、特異メソッドはクラスではなくオブジェクトに直接定義されるメソッドという箇所です。
実は、メソッドは全てクラスに定義されており、特異メソッドも例外ではありません。そのため、オブジェクトに直接定義されるという表現は正確ではありません。
では、特異メソッドはどこに定義されているのかというと、オブジェクトの特異クラスのインスタンスメソッドとして定義されています。
特異クラスとは、ある特定のオブジェクト固有のクラスです。特異クラスは、オブジェクト一つに対して1対1の関係で存在するクラスです。例えば、オブジェクトPeopleの特異クラスは、オブジェクトPeopleのためだけのクラスであり、他のオブジェクトのクラスにはなり得ません。
オブジェクトPeopleの特異クラスのインスタンスメソッドは、そのインスタンスであるオブジェクトPeopleから呼び出すことができます。
これが、オブジェクトPeopleのクラスメソッドの正体です。

  • Classクラス
  • Peopleクラスの特異クラス
  • Peopleクラス
  • Peopleクラスのオブジェクトtanaka

上記の関係性を下記の図で示します。
スクリーンショット 2017-03-07 12.45.24.png

次に実際にコードで確認してみます。
特異クラスはユーザーには意識されないようになっているらしく、classメソッドでは表示することができないため、singleton_classメソッドを使って表示します。

# Peopleの特異クラスを確認
People.singleton_class
# => #<Class:People>

# 特異クラスのクラスを確認
People.singleton_class.class
# => Class

また、オブジェクトPeopleの特異メソッドであるbyeが、実際には特異クラスのインスタンスメソッドとして定義されていることを確認します。

# 特異クラスのインスタンスメソッドにbyeが存在することを確認
People.singleton_class.instance_methods(false)
# => [:bye]

まとめ

  • 特異メソッドは、あるオブジェクト固有のメソッド
  • クラスメソッドは、Classクラスのオブジェクトの特異メソッド
  • 特異クラスは、ある特定のオブジェクト固有のクラス
  • 特異メソッドの真の姿は、オブジェクトの特異クラスのインスタンスメソッド
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.