75
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-24

はじめに

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

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

特異メソッドとは?

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

クラスメソッド

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の特異クラスへ飛び込める!
75
51
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?