Ruby の特異クラスを整理
特異クラスとメソッド探索
- すべてのメソッドはクラスに属する。
- object.class.ancestors が、オブジェクトが呼び出し可能なメソッドを定義しているクラス群である。
ここまでは Ruby 初心者でもよく知るところでしょう。
そして Ruby には特異クラスという概念があることも知っています。
class SampleClass
end
object = SampleClass.new
object2 = SampleClass.new
class << object
def hello
'hello'
end
end
object.hello # => 'hello'
object2.hello # undefined method `hello' for an instance of SampleClass (NoMethodError)
object.class.ancestors # => [SampleClass, Object, PP::ObjectMixin, Kernel, BasicObject]
このように、そのインスタンスだけに固有のメソッドを生やすことが可能です。
上記の例では、hello メソッドは、object の特異クラスに所属しています。
ですがここで矛盾に遭遇します。
- メソッドはすべてクラスに属する。hello メソッドは、object の特異クラスに属する。
object.class.ancestorsが、object が呼び出し可能なメソッドを定義しているクラス群である。- しかし
object.class.ancestorsに、object の特異クラスは含まれていない。
これはどう考えればいいのでしょうか?
結論からいうと、2は「特異クラス」を考慮に入れていないのです。
2は正確には、object.singleton_class.ancestors が、object が呼び出し可能なメソッドを定義しているクラス群である。 が正しいです。
object.singleton_class.ancestors # => [#<Class:#<SampleClass:0x000000011bf4fcd8>>, SampleClass, Object, PP::ObjectMixin, Kernel, BasicObject]
ancestors の0番目が、object の singleton_class を表しています。
では 「object.class.ancestors が、object が呼び出し可能なメソッドを定義しているクラス群である。」という理解は、正しくなかったのでしょうか?
実はこれは、特異クラスを考慮に入れていないだけで、完全な間違いではないのです。
object.singleton_class.superclass == object.class # => true
上記のように、singleton_class が、class を継承していたのです。
この関係により両者は整合します。
なおすべてのオブジェクトに対して、object.singleton_class.superclass == object.class が成り立つのか、というとそれは間違いです。
class SampleSuperClass
end
class SampleClass < SampleSuperClass
class << self
def hello
"hello"
end
end
end
SampleClass.hello # => hello
# どちらも Class になる
SampleClass.class # => Class
SampleClass.singleton_class.superclass.superclass.superclass.superclass # => Class
今度は、
SampleClass.class == SampleClass.singleton_class.superclass.superclass.superclass.superclass # => true
という具合に、singleton_class の superclass をいくつか辿っていくと、class にたどり着きました。
最後にまとめます。
簡単のために、モジュールのミックスインを無視すれば、
- (特異クラスを考慮しない場合)オブジェクトのメソッドは、object.class.superclass.superclass ...etc. と superclass を辿って探索する
- (特異クラスを考慮する場合)オブジェクトのメソッドは、object.singleton_class.superclass.superclass ...etc と superclass を辿って探索する
- 1と2は、object.class == object.singleton_class.superclass.superclass ...etc. の関係が成立するため整合している
と整理することができます。
特異クラスと継承
あるクラスが継承関係にある場合、その特異クラスも継承関係を持ちます。
class SampleSuperClass
class << self
def hello
'hello'
end
end
end
class SampleClass < SampleSuperClass
end
SampleClass.superclass == SampleSuperClass # => true
SampleClass.singleton_class.superclass == SampleSuperClass.singleton_class # => true
これがクラスメソッドが継承される仕組みです。
# SampleClass が継承する、SampleSuperClass の クラスメソッド hello が呼び出される
SampleClass.hello # => hello
# SampleClass が呼び出し可能なメソッドは、以下のクラス群に定義されている
# hello は #<Class:SampleSuperClass> ( SampleSuperClass の特異クラス)に所属する
SampleClass.singleton_class.ancestors
# =>
# [#<Class:SampleClass>,
# #<Class:SampleSuperClass>,
# #<Class:Object>,
# #<Class:BasicObject>,
# Class,
# Module,
# Object,
# PP::ObjectMixin,
# Kernel,
# BasicObject]