Ruby

【Ruby】メソッドの探索方法を理解する


概要

オブジェクトに対してメソッドが呼び出された際にRubyがどの様にメソッドを呼び出しているのかサンプルコードと共にまとめました。


自身のメソッドを呼び出した時の探索方法

class Class1

def hello
puts 'Hello from Class1'
end
end

obj1 = Class1.new
obj1.hello #=> Hello from Class1

p Class1.ancestors #=> [Class1, Object, Kernel, BasicObject]

Rubyのインタプリタはobj1の直接のクラスであるClass1を参照し、そこで定義されているClass1#helloを実行します。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 11.24.59.png


スーパークラスのメソッドを呼び出す際の探索方法

class Class1

def hello
puts 'Hello from Class1'
end
end

class Class2 < Class1
end

obj2 = Class2.new
obj2.hello #=> Hello from Class1

p Class2.ancestors #=> [Class2, Class1, Object, Kernel, BasicObject]

Rubyインタプリタはまずobj2の直接のクラスであるClass2を参照します。

しかし、Class2にはhelloメソッドが存在しないので親クラスであるClass1を参照し、クラス内に定義されているClass1#helloを実行します。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 11.22.49.png


特異メソッドを呼び出した際の探索方法

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

Rubyのインタプリタはオブジェクトのクラスより先に特異クラスを参照し、そのクラス内のメソッドを先に実行します。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 11.34.57.png


クラスにモジュールをミックスインした際のメソッドの探索方法

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
obj2.hello #=> Hello from Module1

p Class2.ancestors #=> [Class2, Module1, Class1, Object, Kernel, BasicObject]

includeされたモジュールはincludeしたクラスの親クラスとの間にあらわれるようになります。

Rubyのインタプリタはまず直接のクラスであるClass2を参照しhelloメソッドを探します。しかし、Class2helloメソッドは定義されていないので、次にModule1の機能を取り込んだクラスを参照し、そのクラス内のhelloメソッドを実行します。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 13.13.26.png


複数のモジュールがミックスインされている際のメソッドの探索方法

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

2つ以上のモジュールが1つのクラスにincludeされている場合にはincludeした順序が後のモジュール内のメソッドが優先的に呼ばれるようになります。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 13.39.42.png


モジュールがプリペンドされている際のメソッドの探索方法

Module#prependModule#includeのように、クラスやモジュールに別のモジュールの機能を取り込むためのメソッドです。

以下がModule#prependの特徴です。



  • Module#includeで取り込んだメソッドと同名のメソッドがModule#prependで入れたモジュールに定義されていた場合Module#prependで入れたメソッドが優先して呼ばれます。


  • Module#prependで組み込んだモジュールは組み込んだクラスの前におかれます。

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 Module3
include Module1
include Module2

def hello
puts 'Hello from Class2'
end
end

obj2 = Class2.new
obj2.hello #=> Hello from Module3

p Class2.ancestors #=> [Module3, Class2, Module2, Module1, Class1, Object, Kernel, BasicObject]

上記でも記しましたように、Module#prependで取り入れられたモジュールは直接のクラスClass2includeされたModule1Module2よりも優先して参照され、その中で定義されているhelloメソッドが呼び出されます。

参照順序は以下の図の通りです。

Screen Shot 2018-07-26 at 13.41.44.png


オブジェクトに存在しないメソッドを呼び出した場合

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
obj2.goodmorning #=> undefined method `goodmorning'(NoMethodError)

p Class2.ancestors #=> [Class2, Module1, Class1, Object, Kernel, BasicObject]

Rubyのインタプリタはメソッドが見つかるまでクラスの継承ツリーを辿っていきます。

そして継承ツリーを一番上(BasicObject)まで辿ってもメソッドが見つからない場合にはBasicObject#method_missingを呼び出します。


メソッド探索方法まとめ


  • (1) Rubyのインタプリンタはレシーバの特異クラスを参照しメソッドを探す。

  • (2) 特異クラスが存在しない場合はレシーバの直接のクラスを参照しメソッドを探す。

  • (3) モジュールがincludeされている場合その中からメソッドを探す。

  • (4) 直接のクラスにメソッドがなくモジュールもincludeされていない場合、継承した親クラスを参照しメソッドを探す。

  • (5) 継承ツリーの一番上のクラスまで辿ってもメソッドが見つからない場合、BasicObject#method_missingを呼び出す。