バンビのブログ

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

Rubyメソッド探索方法

※まず最初に継承チェーンを読んでからメソッド探索方法を読みましょう。

自分が作成したメソッドたちがどのような順序で探索されて、実行されているか覚えておこう!

継承でのメソッド探索

# クラスとメソッド『hello』を作成
class Class1
  def hello
    puts 'Hello from Class1'
  end
end

obj1 = Class1.new

# インスタンスからhelloメソッドを実行する
obj1.hello #=> Hello from Class1

# 継承チェーンで説明したように、Class1が継承しているクラスを確認
p Class1.ancestors #=> [Class1, Object, Kernel, BasicObject]

obj1インスタンは、Class1のメソッドを探索し、実行したメソッドと一致すれば実行される

スーパークラスのメソッドを呼び出すには

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

# Class1を継承してClass2を作成(Class2には何もメソッドを定義していない)
class Class2 < Class1; end

obj2 = Class2.new

# インスタンスからhelloメソッドを実行する(Class2には何も定義されていないので継承したClass1のメソッドが実行される)
obj2.hello #=> Hello from Class1

# 継承したクラス一覧
p Class2.ancestors #=> [Class2, Class1, Object, Kernel, BasicObject]

このようにクラスのなかでメソッドが見つからない場合はスーパークラスへ探索が行われ、更になければまたその先のスーパークラスへ探索が行われます。

特異メソッドを呼び出したときの探索順序は特異メソッドが優先される

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

# インスタンス作成
obj1 = Class1.new

# 特異メソッドを作成
def obj1.hello
  puts 'Hello From Singleton'
end

# メソッドを実行すると特異メソッドが実行されている。
obj1.hello #=> Hello From Singleton

クラスのメソッドより先にオブジェクトに定義された特異メソッドを探索しにいく。

※この特異メソッドはとてもわかりにくいとおもうので別の解説を用意しておくのでそっちを読んでもえると理解の助けになるかもしれない。

ざっくりいうと、特異メソッドは『オブジェクトに定義するメソッドということ』です。 特異メソッドはクラスにメソッドを定義しているのではなく、オブジェクトに定義している。なのでオブジェクト番号が違うとメソッドが実行できない

Mixin(モジュールをincludeすること)のメソッド探索

module Module1
  def hello
    puts 'Hello from Module1'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

#  クラスを継承しつつモジュールをインクルード
class Class2 < Class1
  include Module1
end

obj2 = Class2.new

# Class1よりもModule1のほうが先にメソッド探索が行われ、メソッドが合致したので実行された。
obj2.hello #=> Hello from Module1

# 継承チェーンで説明したようにincludeしたクラスが先に探索され、その後にモジュールが探索される。
p Class2.ancestors #=> [Class2, Module1, Class1, Object, Kernel, BasicObject]

複数Mixin(モジュールをincludeすること)した場合のメソッド探索

module Module1
  def hello
    puts 'Hello from Module1'
  end
end

module Module2
  def hello
    puts 'Hello from Module2'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
  include Module1
  include Module2
end

obj2 = Class2.new

# モジュールはインクルードした下から読み込まれていく
obj2.hello #=> Hello from Module2

# 継承チェーンで書いたのと同様にモジュールは書いた下から読み込んで、メソッド探索が実行される。
p Class2.ancestors #=> [Class2, Module2, Module1, Class1, Object, Kernel, BasicObject]

PrependをつかったモジュールのMixinをした際のメソッド探索順の変更

module Module1
  def hello
    puts 'Hello from Module1'
  end
end

module Module2
  def hello
    puts 'Hello from Module2'
  end
end

module Module3
  def hello
    puts 'Hello from Module3'
  end
end

class Class1
  def hello
    puts 'Hello from Class1'
  end
end

class Class2 < Class1
# prependはモジュールを予め読み込ませておくことができる。
  prepend Module3
  include Module1
  include Module2

  def hello
    puts 'Hello from Class2' 
  end
end

obj2 = Class2.new

# prependを行ったことで読み込みがクラスより先にModule3のメソッドが探索され、実行されている
obj2.hello #=> Hello from Module3

# prependを行ったことで読み込みがクラスより先にModule3が読み込まれている
p Class2.ancestors #=> [Module3, Class2, Module2, Module1, Class1, Object, Kernel, BasicObject]