はじめに
昨晩、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
の関係性は下記図になります。
特異クラスとは何か?
さて、特異メソッドとクラスメソッドの関係は上記の通りですが、実は特異メソッドの説明の中で、正確ではない表現になっていた箇所があります。
それは、特異メソッドはクラスではなくオブジェクトに直接定義されるメソッドという箇所です。
実は、メソッドは全てクラスに定義されており、特異メソッドも例外ではありません。そのため、オブジェクトに直接定義されるという表現は正確ではありません。
では、特異メソッドはどこに定義されているのかというと、オブジェクトの特異クラスのインスタンスメソッドとして定義されています。
特異クラスとは、ある特定のオブジェクト固有のクラスです。特異クラスは、オブジェクト一つに対して1対1の関係で存在するクラスです。例えば、オブジェクトPeopleの特異クラスは、オブジェクトPeopleのためだけのクラスであり、他のオブジェクトのクラスにはなり得ません。
オブジェクトPeopleの特異クラスのインスタンスメソッドは、そのインスタンスであるオブジェクトPeopleから呼び出すことができます。
これが、オブジェクトPeopleのクラスメソッドの正体です。
- Classクラス
- Peopleクラスの特異クラス
- Peopleクラス
- Peopleクラスのオブジェクトtanaka
次に実際にコードで確認してみます。
特異クラスはユーザーには意識されないようになっているらしく、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クラスのオブジェクト)の特異メソッド
- 特異クラスは、ある特定のオブジェクト固有のクラス
- 特異メソッドの真の姿は、オブジェクトの特異クラスのインスタンスメソッド