webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

Rubyをもう一歩進んで勉強する6章(メソッド探索と特異クラス)

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がどのようにメソッドを探索するのかは実務でも使う知識なので、習得しておきたいですね。 

Effective Ruby

Effective Ruby

たのしいRuby 第5版

たのしいRuby 第5版

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング