Rubyのメソッド呼び出し順
※以下の記述は ruby 1.9.1-p129 を前提としています。
Ruby逆引きハンドブックの「SECTION-40 呼び出されるメソッドの決定方法」(p.145)が大変参考になったので、復習を兼ねて実際に試してみました。
基本的にクラスの継承ツリーを辿っていくわけですが、Rubyの場合は特異メソッドやモジュールのMix-inなどが絡んでくるため、そのあたりの関係をちゃんと認識しておく必要があります。
インスタンスメソッド
次のようなコードを書いて実行してみます。
instance_method.rb
#!/usr/bin/env ruby # coding: utf-8 class Object def foo [ "Objectクラスのメソッド" ] end end module SuperIncludeModule def foo [ "親クラスでincludeしたモジュールのメソッド" ] + super end end class SuperClass include SuperIncludeModule def foo [ "親クラスのメソッド" ] + super end end module SubIncludeParentModule def foo [ "自クラスでincludeしたモジュールの親モジュールのメソッド" ] + super end end module SubIncludeModule include SubIncludeParentModule def foo [ "自クラスでincludeしたモジュールのメソッド" ] + super end end class SubClass < SuperClass include SubIncludeModule def foo [ "自クラスのメソッド" ] + super end end bar = SubClass.new def bar.foo [ "特異メソッド" ] + super end module SubExtendModule def foo [ "extendしたモジュールのメソッド" ] + super end end bar.extend SubExtendModule puts bar.foo
実行結果
$ ruby instance_method.rb 特異メソッド extendしたモジュールのメソッド 自クラスのメソッド 自クラスでincludeしたモジュールのメソッド 自クラスでincludeしたモジュールの親モジュールのメソッド 親クラスのメソッド 親クラスでincludeしたモジュールのメソッド Objectクラスのメソッド
include や extend はメソッド定義を上書きするわけではなく、継承ツリーの合間に挟まっていく形になるのだということがわかります。
ちなみに、Rubyではトップレベルで定義された関数は、Objectクラスのプライベートメソッド*1となりますので、上のコードの
class Object def foo [ "Objectクラスのメソッド" ] end end
この部分を
def foo [ "トップレベルのメソッド" ] end
こう書き換えると、
$ ruby instance_method.rb 特異メソッド extendしたモジュールのメソッド 自クラスのメソッド 自クラスでincludeしたモジュールのメソッド 自クラスでincludeしたモジュールの親モジュールのメソッド 親クラスのメソッド 親クラスでincludeしたモジュールのメソッド トップレベルのメソッド
こんな感じの実行結果になります。
クラスメソッド
クラスメソッドの場合はもう少し複雑になります。細かな解説はRuby逆引きハンドブックのp.147あたりを読んで頂くとして、実際にコードを書いて実行してみます。
class_method.rb
#!/usr/bin/env ruby # coding: utf-8 class Object def foo [ "Objectクラスのメソッド" ] end end class Module def foo [ "Moduleクラスのメソッド" ] + super end end class Class def foo [ "Classクラスのメソッド" ] + super end end class SuperClass def self.foo [ "親クラスのクラスメソッド" ] + super end end class SubClass < SuperClass def self.foo [ "自クラスのクラスメソッド" ] + super end end module SuperExtendParentModule def foo [ "親クラスをextendしたモジュールの親モジュールのメソッド" ] + super end end module SuperExtendModule include SuperExtendParentModule def foo [ "親クラスをextendしたモジュールのメソッド" ] + super end end SuperClass.extend SuperExtendModule module SubExtendModule def foo [ "自クラスをextendしたモジュールのメソッド" ] + super end end SubClass.extend SubExtendModule puts SubClass.foo
実行結果
$ ruby class_method.rb 自クラスのクラスメソッド 自クラスをextendしたモジュールのメソッド 親クラスのクラスメソッド 親クラスをextendしたモジュールのメソッド 親クラスをextendしたモジュールの親モジュールのメソッド Classクラスのメソッド Moduleクラスのメソッド Objectクラスのメソッド
ここで注目したいのは SuperClass.extend を SuperClass 及び SubClass の定義の後に実行している部分で、ここからも「継承ツリーに継ぎ足していく形になるので定義の順番は関係ない」のだということがわかります。
複雑な例
以下のようなコードを考えてみます。
complex_mixin.rb
#!/usr/bin/env ruby # coding: utf-8 def foo [ "トップレベル" ] end module ParentModule def foo [ "親モジュール" ] + super end end class SuperClass include ParentModule def foo [ "親クラス" ] + super end end module ChildModule include ParentModule def foo [ "子モジュール" ] + super end end class SubClass < SuperClass include ParentModule include ChildModule def foo [ "自クラス" ] + super end end bar = SubClass.new bar.extend ParentModule puts bar.foo
ParentModule が、
- SuperClass でincludeされている
- ChildModule でincludeされている
- SubClass でincludeされている
- SubClassのインスタンス をextendしている
と、都合4回使われています。この場合 "親モジュール" がどの時点で表れるのか、一瞬考え込んでしまいますが、実行結果は
$ ruby complex_mixin.rb 自クラス 子モジュール 親クラス 親モジュール トップレベル
と、特におかしなところもない、自然な結果になってくれます。
Module#include や Object#extend が、同じモジュールが指定された場合は2回目以降を無視してくれるためですね。
Module#ancestors
Module#ancestors メソッドを使うと、上述のメソッド探索順を簡単に確認することができます。
例えば、先ほどの instance_method.rb の末尾、
puts bar.foo
を
puts bar.class.ancestors
と書き換えて実行すると、
$ ruby instance_method.rb SubClass SubIncludeModule SubIncludeParentModule SuperClass SuperIncludeModule Object Kernel BasicObject
と、大体似たような結果を得ることができます。
ただし、見てわかる通り、ここには 特異クラス は表示されないため、完全な探索経路が得られるわけではないようです。
- 作者: るびきち
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2009/05/25
- メディア: 単行本
- 購入: 24人 クリック: 263回
- この商品を含むブログ (72件) を見る
*1:レシーバを指定できない=関数形式でしか呼び出せないメソッド