かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Ruby メソッドの検索順

初めてのRubyやり終えたのでメモがてら

継承 (親クラス と 子孫クラス)

Ruby のクラスはは 1つだけ親クラスを持つ
多言語とかだと継承って呼ぶ事が多いけど、Rubyも継承って言って良いのかがチョットわからない

同じメソッドがある場合、子孫の持つメソッドが優先される

class Super
  def foo
    p 'Super foo'
  end

   def bar
    p 'Super bar'
  end
end

class Sub < Super
  def foo
    p 'Sub foo'
  end
end

sub = Sub.new
sub.foo # => "Sub foo"
# 子孫クラスで上書きされてないメソッドは親クラスのものが直接呼び出される
sub.bar # => "Super bar"

どのクラスが持っているメソッドか確認する

method(:メソッド名).owner でシンボル形式で渡したメソッドを持っているクラスを確認することができる

p sub.method(:bar).owner
# => Super
p sub.method(:foo).owner
# => Sub
p sub.method(:class).owner
# => Kernel

Module

Module インスタンス化できない機能の集まり (ModuleクラスはClassクラスの親)
Ruby ではクラスは1つしか継承できないが、Module はいくつでも継承?/読込みできる
Module をクラスに追加することを Mix-in と呼ぶらしい

include

include で追加した Module は自身と親の間に挿入されるイメージ
Module に定義されたメソッドは、親クラスのメソッドより優先される

class Super
  def foo
    p 'Super foo'
  end

   def bar
    p 'Super bar'
  end
end

module Mod
  def foo
    p 'foo-foo'
  end

  def bar
    p 'bar-bar'
  end
end

class Sub < Super
  # Module を Mix-in
  include Mod

  def foo
    p 'Sub foo'
  end
end

sub = Sub.new

# 自身の持つメソッドは includeした Module に上書きされない
sub.foo # => "Sub foo"
p sub.method(:foo).owner
# => Sub

# 親クラスの持つメソッドは Module が上書きする
sub.bar # => "bar-bar"
p sub.method(:bar).owner
# => Mod

prepend

Ruby 2.0 で追加された機能
prepend で追加した Module のメソッドは自身の持つメソッドより優先される

module Mod
  def foo
    p 'foo-foo'
  end
end

class Sub
  prepend Mod

  def foo
    p 'Sub foo'
  end
end

sub = Sub.new
sub.foo # => "foo-foo"
p sub.method(:foo).owner
# => Mod

あくまで Mix-in されたクラスのメソッドより優先されるだけなので、親クラスに Module が prepend されていても、子孫クラスのに同名のメソッドがあれば、自身(子孫クラス)のメソッドが呼び出される

module Mod
  def foo
    p 'foo-foo'
  end
  
  def bar
    p 'bar-bar'
  end
end

class Super
  prepend Mod
  
  def foo
    p 'Super foo'
  end
end

class Sub < Super
  def foo
    p 'Sub foo'
  end
end

sub = Sub.new
sub.foo # => "Sub foo"
# 親クラスに Mix-in されたメソッドも呼び出せる
sub.bar # => "bar-bar"

継承順の確認

クラス名.ancestors でModule, 親クラスの継承順を配列で確認することができる

module ModA
end

module ModB
end

module ModC
end

class Super
  include ModA
end

class Sub < Super
  include ModB
  prepend ModC
end

p Sub.ancestors
# => [ModC, Sub, ModB, Super, ModA, Object, Kernel, BasicObject]

ancestors で確認できる配列の先頭から順にメソッドが検索されていくイメージで良さそう。

Module が同じ名前のメソッドを持っている場合、後に追加されたものが優先される

include でも prepend でも同様に後に Mix-in された Module のメソッドが優先される

module ModA
  def hi
    p 'A'
  end
end

module ModB
  def hi
    p 'B'
  end
end

class X
  include ModA
  include ModB
end

X.new.hi # => "B"
p X.ancestors
# => [X, ModB, ModA, Object, Kernel, BasicObject]

class Y
  include ModB
  include ModA
end

Y.new.hi # => "A"
p Y.ancestors
# => [Y, ModA, ModB, Object, Kernel, BasicObject]

class A
  prepend ModA
  prepend ModB
end

A.new.hi # => "B"
p A.ancestors
# => [ModB, ModA, A, Object, Kernel, BasicObject]

class B
  prepend ModB
  prepend ModA
end

B.new.hi # => "A"
p B.ancestors
# => [ModA, ModB, B, Object, Kernel, BasicObject]

特異メソッド ( singleton method )

特異メソッドは、クラスではなくインスタンスに直接生えているメソッド
def オブジェクト.メソッド名 の形で定義する

特異メソッドはクラスが持つメソッドより優先される

module Mod
  def foo
    p 'Mod Foo'
  end
end

class Foo
  prepend Mod

  def foo
    p 'foo'
  end
end

foo = Foo.new

# 特異メソッドを定義
def foo.foo
  p 'FOO FOO'
end

foo.foo # => "FOO FOO"

# 特異メソッドはインスタンスに生えているので `クラス名.ancestors` では確認できない
p Foo.ancestors
# => [Mod, Foo, Object, Kernel, BasicObject]

# `インスタンス.singleton_class.ancestors` で自身のオブジェクトを含んだリストを見ることができる
p foo.singleton_class.ancestors
# => [#<Class:#<Foo:Object ID>>, Mod, Foo, Object, Kernel, BasicObject]

extend

extend は特異クラスに Mix-in をする
extend で追加した Module のメソッドはインスタンス自身の持っている特異メソッドは上書きしない
特異メソッド版の include

module ModA
  def foo; p 'Mod Foo' end

  def bar; p 'Mod Bar' end
end

module ModB
  def bar; p 'B' end
end

module ModC
  def bar; p 'C' end
end

class Foo
  prepend ModB
  include ModC

  def foo
    p 'foo'
  end
  
  def bar
    p 'bar'
  end
end

foo = Foo.new

# Mod を特異クラスに Mix-in
foo.extend Mod

# 特異メソッドを追加
def foo.foo
  p 'FOO FOO'
end

# 自身の特異メソッドは extend した Module より優先される
foo.foo # => "FOO FOO"
# extend した Module のメソッドはクラスの持つインスタンスメソッドより優先される
foo.bar # => "Mod Bar"

# extend で追加した Module は自身のインスタンの次に追加されている
p foo.singleton_class.ancestors
# => [#<Class:#<Foo:Object ID>>, ModA, ModB, Foo, ModC, Object, Kernel, BasicObject]

まとめ

Ruby のメソッドの検索は下記の順で遡って検索する

  1. 特異メソッド
  2. extendインスタンスに追加された Module のメソッド (特異メソッド)
  3. prepend で追加された Module のメソッド
  4. クラス自身の持つインスタンスメソッド
  5. include で追加された Module のメソッド
  6. クラス内にメソッドが見つからない場合は親クラスを探索
  7. クラスの親を遡って一番上のObject クラスまで探索してメソッドが見つからなければ BasicObject#method_missing が呼び出され NoMethodError が発生

メソッドの順番を確認したくなったら クラス名.ancestorsインスタンス.singleton_class.ancestors で継承順のリストを確認できる
どのクラスがメソッドを持っているか確認したい時は インスタンス.method(:メソッド名).owner で確認することができる

JavaScript の prototype 継承っぽくてすんなり理解できた!


[参考]

プログラミング言語 Ruby

プログラミング言語 Ruby