#はじめに
メソッドを呼ぶ際に、そのメソッドは自身のクラス内に定義されている場合の他にも
スーパークラス内や、module、又はクラスから作成されたインスタンスに特異メソッドとして定義されているなど
様々なケースがあるかと思います。
今回は、それらのケースの、メソッドの探索の流れについて
まとめていきたいと思います。
自身のメソッド
class Klass
def hello
puts "Hello_from_Klass"
end
end
klass_obj = Klass.new
klass_obj.hello
# => Hello_from_Klass
最も単純なケースかと思われます。
obj
オブジェクトに対してhello
メソッドが呼ばれた際、
直接のクラスであるKlassクラスを参照し、そこにhello
メソッドがないかを探します。
今回の場合は、そこで見つけることが出来たので、そこで探索は終了しそのメソッドを実行します。
スーパークラスのメソッド
class Klass
def hello
puts "Hello from Klass"
end
end
class SubKlass < Klass
end
sub_klass_obj = SubKlass.new
sub_klass_obj.hello
# => Hello_from_Klass
探索方向は、まず直接のクラスを参照し、そこになければ
その親クラス、なければ更にその親クラス・・・
のようにクラスの継承順序と等しくなります。
今回のケースでは、SubKlass
を参照しhello
メソッドが無かったため
その親クラスであるKlass
を参照し、hello
メソッドが定義されていたため
探索は終了し、メソッドを実行します。
特異メソッド
class Klass
def hello
puts "Hello from Klass"
end
end
obj = Klass.new
def obj.hello
puts "Hello from singleton"
end
obj.hello
# => Hello from singleton
このケースでは、直接のクラスに定義したhello
メソッドではなく
特異メソッドとして定義したhello
メソッドが呼ばれました。
つまり、特異クラスはオブジェクトのクラスより先にメソッドが探索されていることが
わかります。
クラスにmixinされたメソッド
スーパークラスとmoduleに同名メソッドを定義して
サブクラスのインスタンスからそのメソッドを呼んでみます。
module HelloModule
def hello
puts "Hello from HelloModule"
end
end
class Klass
def hello
puts "Hello from Klass"
end
end
class SubKlass < Klass
include HelloModule
end
sub_klass_obj = SubKlass.new
sub_klass_obj.hello
# => Hello from HelloModule
includeしたmoduleのhello
メソッドが呼ばれました。
includeされたmodulueは、includeしたクラスの親クラスとの間に
存在することになります。
この時、moduleをラップしたかのような無名クラスが作られ継承ツリーの中に埋め込まれています。
ちなみに、この無名クラスはRubyのプログラムからは触れることが出来ません。
このように無名クラスが作成され、継承ツリーに埋め込まれるため、
メソッドの探索も通常の継承が行われている時と同じルールで参照することができています。
複数のmoduleをinclude
先ほどは、1つのmoduleのみをincludeさせましたが、
moduleのincludeはクラスの継承と異なり
1つのクラスに対して複数のmoduleをincludeすることが出来ます。
実際に複数のmoduleをincludeさせてメソッドを呼んでみます。
module FirstModule
def hello
puts "Hello from FirstModule"
end
end
module SecondModule
def hello
puts "Hello from SecondModule"
end
end
class Klass
def hello
puts "Hello from Klass"
end
end
class SubKlass < Klass
include FirstModule
include SecondModule
end
sub_klass_obj = SubKlass.new
sub_klass_obj.hello
# => Hello from SecondModule
複数のmoduleをincludeさせた場合、
最後にincludeさせたmoduleのメソッドが呼ばれました。
このように複数のmoduleを同一のクラスにincludeさせた場合、
includeさせた順にmoduleはそのクラスの継承ツリーに埋め込まれていきます。
まず、FirstModuleがincludeされ以下の継承ツリーになります。
その後、下に記述してあるSecondModuleがincludeされると、以下のようになります。
moduleをincludeした後に、再度include
今度は、先ほどのようにFirstModule``SecondModule
の順に読み込んだ後に
再度FirstModule
を読み込んでみます。
module FirstModule
def hello
puts "Hello from FirstModule"
end
end
module SecondModule
def hello
puts "Hello from SecondModule"
end
end
class Klass
def hello
puts "Hello from Klass"
end
end
class SubKlass < Klass
include FirstModule
include SecondModule
include FirstModule
end
sub_klass_obj = SubKlass.new
sub_klass_obj.hello
# => Hello from SecondModule
最後にincludeの記述があるのはFirstModule
ですが
呼ばれたのはSecondModule
のメソッドでした。
実は継承ツリーにmoduleをラップしたクラスを差し込む際に、
継承ツリーに同じmoduleがないかを確認しています。
そのため、2回目のinclude FirstModule
が呼ばれた際に、
既にFirstModule
がincludeされているため
差し込まれずにincludeメソッドが終了します。
moduleをprepend
module PrependModule
def hello
puts "Hello from PrependModule"
end
end
module IncludeModule
def hello
puts "Hello from IncludeModule"
end
end
class Klass
prepend PrependModule
include IncludeModule
def hello
puts "Hello from Klass"
end
end
klass_obj = Klass.new
klass_obj.hello
# => Hello from PrependModule
includeはmoduleをラップした無名クラスを
includeしたクラスより上位に埋め込むのに対して
prependはそのクラスより下位に埋め込まれます。(prependしたクラスより優先的にメソッドが呼ばれる)
そのため、prependするmodule内でsuper
の呼び出しを行うと、
prependしたクラスの同名メソッドが呼ばれます。
オブジェクトに存在しないメソッドを呼び出す
module HelloModule
def hello
puts "Hello from FirstModule"
end
end
class Klass
def hello
puts "Hello from Klass"
end
end
class SubKlass < Klass
include HelloModule
end
obj = SubKlass.new
obj.bye
# => undefined method `bye' for #<SubKlass:0x00007fb5fb80c628> (NoMethodError)
オブジェクトのどこにも存在しないbye
メソッドを呼んでみたところ
NoMethodError
を吐きました。
byeメソッドが呼ばれると継承ツリーを順に辿ってそのメソッドを探していきます。
しかし、今回はどこにも定義していないため継承ツリーの最上位であるBasicObject
まで探した後
直接のクラスであるSubKlass
まで戻り、今度はmethod_missing
メソッドを継承ツリーの順番に探していきます。
今回は自分でmethod_missing
メソッドを定義していないため最上位である
BasicObject
のmethod_missing
メソッドが呼ばれNoMethodError
を例外として発生させます。
そのため、BasicObject
までの継承ツリーの途中でmethod_midding
メソッドがあれば
それを呼び出します。
module HelloModule
def hello
puts "Hello from FirstModule"
end
end
class Klass
def hello
puts "Hello from Klass"
end
def method_missing(method_name, *args)
puts "#{method_name}?そんなメソッドないよ"
end
end
class SubKlass < Klass
include HelloModule
end
obj = SubKlass.new
obj.bye
# => bye?そんなメソッドないよ
まとめ
探索の流れとして
1 , レシーバの特異クラス
2 , 直接のクラス
2', moduleがincludeされていればmodule
3 , 2の親クラス
3', moduleがincludeされていればmodule
4 , 3
をメソッドが見つかるまで繰り返す
5 , 継承ツリーの最上位(BasicObject
)まで探索しなければ、そのメソッドでの探索を終了
6 , メソッド名を引数にして、method_missing
メソッドを1
から探していく
7 , なければBasicObject
のmethod_missing
が呼ばれNoMethodError
の例外が発生