バンビのブログ

駆け出しのエンジニアです!日々の疑問など備忘録として書いていきます。

特異メソッドと特異クラス

この特異メソッドと特異クラスっていつも見かけるけど、なんやねん。 読んでも読んでもわからんってちょうど一年前に思ったので解説します。

特異メソッドとは

単一オブジェクトに対して定義できるメソッドのことをさします。

クラスメソッド

class Test

# selfでクラスを指定しているよ!
  def self.foo
    'foo!!'
  end
end

p Test.foo
# => "foo!!"



class Test

# selfではなく、明示的にクラス名を書くこともできるよ!どっちも同じ意味です。
  def Test.foo
    'foo!!'
  end
end

p Test.foo
# => "foo!!"

クラスメソッドはそのクラスの特異メソッド(Singleton Method)という意味になります。ここがよくわからんポイントです。

これの singleton_methods を使うとそのオブジェクトの特異メソッドが確認できます。
p Test.singleton_methods
# => [:foo]

この特異メソッドというものは、オブジェクトに対してメソッドが定義されていることになります。
オブジェクトはPCのメモリに『オブジェクト番号』みたいなものが振られていて、それぞれまったく異なった番号で管理されていて、

変数・定数・インスタンスとかそれぞれに全く別の『オブジェクト番号』が振られていて、そのオブジェクトごとにメソッドを定義していると考えるとわかりやすいです。

なので、オブジェクトごとにメソッドを設定してる例がこちらになります。

# 上の例で作成したTestクラスをインスタンス化します。
# test1を(オブジェクト番号1とします。)
test1 = Test.new

# Testクラスからできた test1(オブジェクト番号1) インスタンスに特異メソッドを定義します。
def test1.bar
  'bar!!'
end

# test1(オブジェクト番号1)に定義していたので実行できました。
p test1.bar
# => "bar!!"

では、次に同じTestクラスからtest2(オブジェクト番号2)で bar メソッドを実行しましょう。

# 同じTestクラスをインスタンス化
test2 = Test.new
test2.bar
# 定義されてないのでエラーが発生しました。
# => undefined method `bar' for #<Test:0x007fe2e0838018> (NoMethodError)

というように同じクラスから生成したインスタンスなのに同じメソッドが実行できないというようになりました。
その理由は、オブジェクトtest1に対してメソッドを定義しているので、オブジェクトのtest2には定義していなかったら実行できないということになります。

このTestクラスも実は、Classクラスから生成されたインスタンスなので、

はじめに書いたself(またはクラス名).メソッドは、特異メソッドをクラスに定義していることになります。

クラスもオブジェクトなので、インスタンスに特異メソッドを定義したときと同じ考え方になります。

わかりにくいけど、インスタンスの規模が大きくなったとおもうと良いかも!

特異メソッドは特異クラスに定義されている

クラスメソッドはクラスにself.methodで定義できて、インスタンスメソッドは、クラス内に、def メソッド名 endで定義できます。 じゃあ、特異メソッドはどこに定義されるのか、それは特異クラスに定義されることになります。

# Testクラス作成
class Test
end

# Testクラスを継承してExtTestを作成
class ExtTest < Test
end

test = ExtTest.new

# testインスタンスのクラスはExtTest
p test.class
# => ExtTest

# testインスタンスのスーパークラスはTestクラス
p test.class.superclass
# => Test


# インスタンスごとにオブジェクト番号が違います。
test1 = ExtTest.new               #=> #<ExtTest:0xXXXX>
test2 = ExtTest.new               #=> #<ExtTest:0xYYYY>


# testインスタンスの特異クラスは#<ExtTest:0x007fc6a2038020>という特異クラス名とオブジェクト番号が返ってきます。
p test.singleton_class
# => #<Class:#<ExtTest:0x007fc6a2038020>>

# testインスタンスの特異クラスの名前だけ返しています。
p test.class.singleton_class
# => #<Class:ExtTest>

# どちらもスーパークラスの特異クラスを返しています。
p test.class.singleton_class.superclass
# => #<Class:Test>
p test.class.superclass.singleton_class
# => #<Class:Test>