Rubyでは、継承しているクラスを上へ上へと昇りながらメソッド探索を行います。
一つ上のクラスはsuperclassメソッドで知ることができますが、モジュールをインクルードしたときはsuperclassで検出できず、しかしいつの間にかモジュールのメソッドが使えるようになっています。
今回はその仕組みを紐解いていきます。
そのためにまずは、オブジェクト、クラス、モジュールについて用語を復習しておきます。
スポンサーリンク
用語の復習
- オブジェクト
オブジェクトはインスタンス変数と呼ばれる変数の入れ物です。
インスタンス変数はオブジェクトの状態を表しています。
各オブジェクトは一つのクラスと結び付けられる特殊な内部変数を持っています。
- クラス
クラスはメソッドと定数の入れ物です。
メソッドはそのクラスのインスタンスのふるまいを表現しています。
クラス自体もClassクラスのインスタンスです。
- スーパークラス
あるクラスが継承している親クラスです。
クラスはスーパークラスを管理するための特別な内部変数を持っています
- モジュール
Moduleクラスのインスタンスであるという点を除いて、クラスと全く同じです。
Classクラスのインスタンスでないため、newメソッドを持ちません。
モジュールで定義したメソッドは、クラスにインクルードすることで使うことができます。
また、継承と異なり複数のモジュールを一つのクラスにインクルードすることができます。
- 特異クラス
継承階層に含まれる、名前のない不可視のクラスを指します。
クラスメソッドやモジュールのメソッドを格納し、継承階層に加えることで、メソッドを探索可能にしています。
- レシーバ
メソッドが呼び出されるオブジェクトのことです。
オブジェクトに焦点が当たっている場合はインスタンスと呼ぶが、メソッドに焦点が当たっている場合はレシーバと呼ぶ。
customer.name
のcustomerのこと
メソッド探索
以下のような継承関係にあるクラスたちのメソッド探索を見てみます。
class A def name "a" end end class B < A end class C < B end
このとき、クラスCのインスタンスがnameメソッドを呼び出すとき、まずは自分自身がnameメソッドを持っていないか探索し、なければ一つ上のクラスBを探索します。
Bも持っていないので、さらに一つ上のクラスAを見に行きます。ここでnameメソッドがみつかったので値を返します。
ここで、もしAもnameメソッドを持っておらず、階層を上がり続けルートクラス(BasicObjectクラス)にも存在しなかったときは、出発点へ戻り、今度はmethod_missingというメソッドの探索を始めます。
method_missingは定義されていないメソッドが呼ばれたときに呼ばれる特別なメソッドです。
以上が通常のメソッド探索でした。
では、クラスにモジュールがインクルードされている場合の探索はどうなるでしょうか。
次の例を見てみます。
class A end module B def name "b" end end module C def name "c" end end class D < A include(B) include(C) end
継承の塔はD < A < Object < BasicObject
となっていますが、どのクラスにもnameメソッドは実装されていません。
しかし、実際はクラスDのインスタンスはnameメソッドが呼ばれるとある値を返します。
ここでRubyが行っていることは、インクルードされたモジュールを特異クラスにリンクし、継承階層に(そっと)追加します。
追加の順番は最後に書かれたものが先に追加されます。
本来この追加された特異クラスは見えませんが、あえて見えるように書くと次のようになります。
D < C(module Cの特異クラス) < B(module Bの特異クラス) < A < Object < BasicObject
なので、nameメソッドが呼ばれると、モジュールCのnameが呼ばれるため、"c"が返されます。
この仕組みに似たもので、特異メソッドがあります。
特異メソッド
特異メソッドとは次のように定義されたメソッドです。
d = D.new def d.name "d" end
このとき、インスタンスdがnameメソッドを呼ぶと"d"が返されます。もちろん、クラスDにメソッドを追加したわけではないので、別のインスタンスを作っても"d"は返ってきません。
メソッド探索は次のようになります。
d.nameを定義した時点で、nameメソッドを持つ特異クラスが作られ、インスタンスdのクラスとしてこの特異クラスが差し込まれます。
そして、この特異クラスの一つ上の階層のクラスがDになります。
インスタンスdとクラスDの間に特異クラスを挟み込むことによって、インスタンスdだけにメソッドが定義されたように見えるわけです。
最後はクラスメソッドについてです。
- クラスメソッド
クラスメソッドは次のように定義されるものです
class Customer def self.self_name "G" end end
ここで、selfはCustomerクラス自身を指します。
クラスメソッドは、Customer.self_name
のようにクラスをインスタンス化せずに使えるメソッドです。
これまでの類推から、クラスメソッドも特異メソッドと同じ形をしていることがわかります。
また、クラス自体もClassクラスのインスタンスであることから、クラスメソッドを定義するということは、
「CustomerインスタンスとClassクラスとの間に、self_nameメソッドを持つ特異クラスを挟み込むこと」と言えます。
そうすることで、Customerはself_nameメソッドを呼び出されると、自身のクラス(Classクラス)ではなく、特異クラスの方を探索しにいきます。
特異メソッド、特異クラスはややこしい概念ですが、理解できたでしょうか?
Rubyがどのようにメソッドを探索するのかは実務でも使う知識なので、習得しておきたいですね。
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
- 作者: 高橋征義,後藤裕蔵,まつもとゆきひろ
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2016/02/26
- メディア: 単行本
- この商品を含むブログ (2件) を見る
Ruby on Rails 5アプリケーションプログラミング
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/14
- メディア: 大型本
- この商品を含むブログを見る