特異クラスについて考える前に
特異クラスについて学ぶ前に、まずは特異メソッドとメソッド探索について知っておく必要があります。
特異メソッドとは
特定のオブジェクトに対して定義されたメソッドのことです。
wareki = "heisei"
def wareki.is_reiwa?
self == "reiwa"
end
wareki.is_reiwa? #=>false
ここで定義したwareki.is_reiwa?はStringクラスの他のオブジェクトに影響を及ぼすことはありません。warekiというオブジェクト(正確には"heisei")に対してのみ適用されるメソッドです。
特異とは普通と異なり特別なことです。特異メソッドはその言葉通り、普通のメソッドとは異なり、特別に定義されたメソッドのことです。
(英語ではsingular methodと呼称されている。singularには「個別の」という意味があるので、「個別に定義されたメソッド」と理解する方がしっくり来るかもしれません)
クラスメソッドと特異メソッドの関係
class Hoge
def self.class_method_a
puts "class_method_a"
end
end
Hoge.class_method_a #=> "class_method_a"
プログラミング言語を少しでも触ったことのある人なら、上のコードが何をやっているかわかると思います。
Hogeクラスにclass_method_aクラスメソッドを定義しています。
これを先の特異メソッドと見比べてみてください。
「なんか似てない?」
実はクラスメソッドも一種の特異メソッドなのです。
「え?でも特異メソッドってオブジェクトに対して定義するものじゃないの?」
「クラスってオブジェクトじゃなくね?」
と思うかもしれないが、実はクラスもオブジェクトです(ややこしい)。
もっとややこしい言い方をすると「HogeクラスはClassクラスのオブジェクト」です。
クラスメソッド = Hogeクラス(Classクラスの「オブジェクト」)に定義されたメソッド
特異メソッド = 特定のオブジェクトに対して定義されたメソッド
クラスメソッド == 特異メソッド
以上のことからクラスメソッドも特異メソッドの一種なのです。
メソッド探索
メソッド探索とは、その名の通り、呼ばれたメソッドを探すことです。
class A
def hoge
puts "hoge"
end
end
class B < A; end
obj_b = B.new
obj_b.hoge #=> "hoge"
obj_bがhogeを呼んだ時rubyの中では何が起こっているのでしょうか。
rubyは以下の2つのことを行います。
・メソッドを探す(メソッド探索)
・メソッドを実行する
ではメソッド探索をする時、rubyはどのように動いているのでしょうか。
※レシーバ : 呼び出したメソッドが属するオブジェクト。hogeで言うobj_b
rubyはレシーバの「クラス」に入り、目的のメソッドが見つかるまで継承チェーンを上っていきます。
「ん?レシーバのクラスに入る?」
「レシーバ自体は探索しないの?」
そうです。メソッド探索はレシーバのクラスから探索を始めます。
ここにrubyの秘密があります。
実はオブジェクトのメソッドは全てクラスに属しており、オブジェクトはクラスが持っているメソッドを参照しているだけなのです。
だからメソッド探索は「レシーバのクラス」から探索を始めるようになっています。
obj_b.hogeの話に戻ります。
ではobj_b.hogeを呼んだ時、rubyはまず何処を探すでしょうか?
そう、obj_bのクラスであるBです。
しかし、クラスBにはhogeメソッドが定義されていません。
では次に何処を探すでしょうか?
rubyは「目的のメソッドが見つかるまで継承チェーンを上っていく」ので、次に探すのはクラスAです。
クラスAを探して、ついにhogeメソッドを見つけることができました。
以上がrubyのメソッド探索です。
ここで覚えておくべきことは
・rubyはレシーバのクラスに入り、目的のメソッドが見つかるまで継承チェーンを上っていく。
・メソッドは全てクラスが持っている。オブジェクトはクラスが持っているメソッドを参照しているに過ぎない。
以上の2つです。
特異クラスとは
ここからやっと本題に入ります。
特異クラスとはなんなのでしょうか。
特異クラスの存在を認識するために、まずは特異メソッドを定義してみましょう。
class MyClass; end
obj = MyClass.new
def obj.singleton_method; puts "singleton"; end
これでobjの特異メソッドとしてsingleton_methodを定義することができました。
ではこのメソッドはどこに定義されたのでしょうか。
メソッド探索のルールに従うと、rubyはレシーバのクラスに入り、目的のメソッドが見つかるまで継承チェーンを上っていきます。
ということは、singleton_methodはobjのクラスであるMyClassに定義されたということでしょうか。
いや、MyClassに定義されていたとしたら、それはMyClassのインスタンスメソッドになります。特異メソッドではありません。
では、どこに定義されたのでしょう。
ここで登場するのが特異メソッドです。
実はobjはMyClass以外にもクラスを持っていおり、それが特異クラスと呼ばれています。
特異クラスは、インスタンスのクラスを調べるためにいつも使うObject#classの結果には現れません。
Object#singleton_classを使うと特異クラスを参照することができます。
str = "abc"
str.class #=> String
"abc".singleton_class #=> #<Class:#<String:0x00007f84af86fcf0>>
特異クラスには特徴が3つあります。
1つ目に、特異クラスはインスタンスを1つしか持つことができません。
2つ目に、特異クラスは継承することができません。
最後に、特異クラスはオブジェクトの特異メソッドが存在する場所です。
特異クラスの特徴を理解した上で、クラスメソッドについて考えてみましょう。
先にクラスメソッドは特異メソッドという説明をしました。
特異メソッドということは、それは特異クラスに定義されたメソッドである必要があります。
つまりクラスそのものにも特異クラスが存在するのです。
class Hoge
self.p
puts "hogehoge"
end
end
このようにHogeクラスに対してクラスメソッドpを定義した時、そのメソッドはHogeクラスの特異クラスに定義されます。
特異クラスはインスタンスを1つしか持つことができないので、クラスメソッドpはHogeクラスのみに適用されます。