はじめに
アドベントカレンダー、何書くか非常に迷いました。。
特異メソッドについて書いてみようと思います。
間違い等あればご指摘お願いします!
特異メソッドとは?
単一のオブジェクトに特化したメソッドのことらしいです。
クラスメソッドとかはよく見ますね。
クラスメソッド
class Test
def self.foo
'foo!!'
end
end
p Test.foo
# => "foo!!"
よく見るやつですね。
クラス名.foo
的な感じで呼び出せるメソッドです。
クラス定義の中ではselfはクラスを指すので、以下と同一です。
class Test
def Test.foo
'foo!!'
end
end
p Test.foo
# => "foo!!"
クラスメソッドはそのクラスの特異メソッド(Singleton Method)です。
先ほどのTestクラスの特異メソッドを確認すると、、
p Test.singleton_methods
# => [:foo]
fooメソッドが特異メソッドであることが確認できます。
obj.method の形で定義
すべてのクラスはClassクラスのインスタンスです。
ということで先ほどのクラスメソッドの定義と同じように、何らかのインスタンスに対してdef obj.method
のような形でメソッドを定義してみます。
test1 = Test.new # => 先ほどのTestクラスをインスタンス化
def test1.bar
'bar!!'
end
p test1.bar
# => "bar!!"
クラスメソッドの定義の例では、ClassクラスのインスタンスであるTestクラスの特異メソッドを定義していましたが、
ここではTestクラスのインスタンスであるtest1の特異メソッドを定義しました。
このbarメソッドはTestクラスに定義されたわけではないため、おなじTestクラスのインスタンスであっても呼び出すことはできません。
test2 = Test.new # => 同じTestクラスをインスタンス化
test2.bar
# => undefined method `bar' for #<Test:0x007fe2e0838018> (NoMethodError)
メソッドはクラスに属しますが、それでは特異メソッドはどこに定義されるのでしょうか?
正解は特異クラス(Singleton Class)です!
特異クラスとはオブジェクトの裏側に潜んでいるようなクラスです。
特異クラス
特異クラスはクラスというだけあって、やはりClassクラスのインスタンスです。
さらにややこしいですが、クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラスという関係が成り立ちます。
ややこしい。。
class Test
end
class ExtTest < Test
end
test = ExtTest.new
p test.class
# => ExtTest
p test.class.superclass
# => Test
p test.singleton_class
# => #<Class:#<ExtTest:0x007fc6a2038020>>
p test.class.singleton_class
# => #<Class:ExtTest>
p test.class.singleton_class.superclass
# => #<Class:Test>
p test.class.superclass.singleton_class
# => #<Class:Test>
特異クラスはputsとかで出力すると#がつきます。
ancestorsで見てみる
ancestorsは継承チェーンを配列で返すメソッドです。
p test.singleton_class.ancestors
# => [#<Class:#<ExtTest:0x007fa6919f0238>>, ExtTest, Test, Object, Kernel, BasicObject]
p test.class.singleton_class.ancestors
# => [#<Class:ExtTest>, #<Class:Test>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
特異クラスのスーパークラスは、自身の属するクラスのようです。
メソッド探索の順序は、特異クラス → 自身のクラス(特異クラス目線ではスーパークラス) → スーパークラス → 以下同文...
となります。
特異クラスを開けてみる
特異クラスは普段は見えませんが、特異クラスのスコープに入ることもできます。
class Test
p self
# => Test
end
class << Test
p self
# => #<Class:Test>
end
selfが特異クラスを指しています!
class << obj
のようにすれば、その中は特異クラスのスコープになります。
これを使えば、下記のように複数のクラスメソッドを定義する場面で、
class Test
def self.foo
'foo!!'
end
def self.bar
'bar!!'
end
def self.baz
'baz!!'
end
end
p Test.singleton_methods
# => [:foo, :bar, :baz]
selfがたくさん並びますが、これを
class Test
class << self
def foo
'foo!!'
end
def bar
'bar!!'
end
def baz
'baz!!'
end
end
end
p Test.singleton_methods
# => [:foo, :bar, :baz]
このように書けます!
define_singleton_method
define_singleton_methodメソッドは、レシーバのオブジェクトに対して特異メソッドを定義するメソッドです。
引数にシンボルまたは文字列を渡し(メソッド名になる)、渡したブロックがメソッドの本体になります。
ブロック変数はメソッドの引数となります。
array = []
# 特異メソッドとして、引数を1つ受け取るaddメソッドを定義
array.define_singleton_method :add do |item|
self << (item * 2)
end
array.add 'foo'
array.add 'bar'
array.add 'baz'
p array
# => ["foofoo", "barbar", "bazbaz"]
p array.singleton_methods
# => [:add]
上記ではArrayクラスのインスタンスであるarrayの特異メソッドとしてaddメソッドを定義しています。
ちなみにdefine_singleton_methodメソッドと似たようなメソッドとして、
define_methodメソッドがありますが、こちらはインスタンスメソッドを定義するメソッドになります。
まとめ
- クラスメソッドの定義は特異メソッドの定義
- メソッド探索は特異クラスへ進んでスーパークラスへ!
-
class << obj
でobjの特異クラスへ飛び込める!