どうも、明日RubyGoldの試験を受ける予定のものです。
さてRubyGoldの勉強している最中に、ハマった部分がModule内とClass内のsuperの挙動です。結局module内のsuperやclass内のsuperがどこのメソッドを起動させるのかがいまいちわからないということがおきていました。
そこで今回はサンプルコードを用いてModule内とClass内のsuperの挙動をまとめておきたいと思います。結論は「superによってancestorsの順番に同名メソッドが探索される」ということです。
【Ruby Gold】Module内とClass内のsuperの挙動でハマった箇所
prepend Mの場合の挙動
扱うコードは以下のコードです。今回はmodule Mをprependしています。
module M
def foo
super
puts "M#foo"
end
end
class C2
def foo
puts "C2#foo"
end
end
class C < C2
def foo
super
puts "C#foo"
end
prepend M
end
C.new.foo
ちなみにこれを実行するとどう表示されるか考えてみてください。
これで表示される順序は「C2#foo、C#foo、M#foo」です。
以下が考え方の手順です。
- まずC.ancestorsの結果を確認しておきます。つまり実行順序ですね。この場合はprepend Mなので「M, C, C2」の順番で実行されます。
- 実行順序で最初に実行されることになっているM#fooが実行されます。その中にsuperが入っていますね。ここでポイントですがM#foo内のsuperで起動するのはancestorsで直後に実行される予定のC#fooです。C2#fooではありません。
- C#fooが実行されますが、その中にもsuperの記述があります。これはancesutorsの順序の直後にあるC2#fooが呼び出されます。
- 結果としてC2での処理によって、 C2#fooがまず表示されます。
- 続いて処理がCに戻って、C#fooが表示されます。
- 最後にMに処理が戻って、M#fooが表示されます。
ポイントはancestorsの順番で同名のメソッドが呼び出されていくということです。superというとスーパークラスのメソッドを呼ぶと思いがちですが、オーバーライドされているメソッドを呼び出すのが基本ですので注意してください。
include Mの場合の挙動
先ほどと、ほとんど同じコードですが、module Mがincludeされています。
module M
def foo
super
puts "M#foo"
end
end
class C2
def foo
puts "C2#foo"
end
end
class C < C2
def foo
super
puts "C#foo"
end
include M
end
C.new.foo
このコードを実行するとどのような順番で表示されるか考えてみてください。
これで表示される順序は「C2#foo、M#foo、C#foo」です。
この挙動を理解する手順は以下の通りです。
- まずは実行順序を考えます。今度はinclude Mなので、C.ancestorsの結果はC, M, C2になっています。
- C#fooメソッドが実行されます。ここでのポイントはC#fooの中に記述されているsuperはC2#fooではなく、M#fooを呼び出します。理由は探索順序に沿ってメソッドが探索されるためです。つまりCの次に探索されるのはMですから、superはMのメソッドを呼び出します。
- M#fooが実行されると、superが記述されていますので、今度はancestorsの最後のC2に記述されている同名メソッドfooを呼び出します。
- 結果としてC2#fooがまず表示されます。
- 続いて処理がMに戻ってM#fooが表示されます。
- 最後にCに処理が戻ってC#fooが表示されます。
まとめ
今回のポイントは、superはancestorsで表示されている順序でメソッドを探索するということです。だからclass Cの中にsuperが書いてあっても、ancestorsの順序でMが先ならMの同名メソッドが先に実行されるということですね。
かなりハマった部分なので注意してください!