Help us understand the problem. What is going on with this article?

【Ruby】特異メソッドについて

More than 3 years have passed since last update.

はじめに

アドベントカレンダー、何書くか非常に迷いました。。

特異メソッドについて書いてみようと思います。
間違い等あればご指摘お願いします!

特異メソッドとは?

単一のオブジェクトに特化したメソッドのことらしいです。
クラスメソッドとかはよく見ますね。

クラスメソッド

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の特異クラスへ飛び込める!
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away