yohasebe.com


Ruby の動的オブジェクト生成について

以下は Pete Lacey 氏による Idiomatic Dynamic Ruby の抄訳(というか超訳・・・)です。誤りやタイポなど、お気づきの点はご教示ください。

Ruby の動的な特性を理解するには、Ruby のオブジェクト生成を実際にトレースしていくのが近道だ。これにより、

sing = class << self; self; end

のようなメタプログラミングのイディオムも「納得して」使えるようになる。次のコードを実行してみよう。

class Test
  puts "1. 基底クラス内、全てのメソッドの外側。" +
       "object ID は #{self.object_id}"

  def meth
    puts "9. 基底クラスのインスタンスメソッド内。" +
         "object ID は #{self.object_id}"
  end

  class << self
    puts "2. 基底クラスの特異クラス内。" +
         "object ID は #{self.object_id}"

    def meth2
      puts "4. 基底クラスのクラスメソッド内。" +
           "object ID は #{self.object_id}"
      sing = class << self; self; end;
      puts "5. sing は特異メソッドを参照。" +
           "object ID は #{sing.object_id}"
    end
  end
end

class Atest < Test
  puts "3. 基底クラスのサブクラス内。" +
       "object ID は #{self.object_id}"
end

puts "-- 基底クラスのメソッドを直接コール。"
Atest.meth2

puts "6. 基底クラスのサブクラスの外。" +
     "サブクラス Atest の Object ID は #{Atest.object_id}"

class << Atest
  puts "7. サブクラスの特異クラス内。" +
       Object ID#{self.object_id}"
end

puts "-- サブクラスのインスタンスを作成。"
a = Atest.new
puts "8. インスタンスオブジェクトの Object ID は #{a.object_id}"

puts "-- インスタンスメソッドをコール"
a.meth

class << a
  puts "10. インスタンスオブジェクトの特異クラス内。" + 
       "Object ID は #{self.object_id}"
end

Ruby の動的性質の要は特異クラス(singleton class)にある。人によってはこれを仮想
クラス(virtual class)、メタクラス(metaclass)、アイゲンクラス(eigenclass)などと呼んだりもする。Ruby ではあらゆるオブジェクトが特異クラスを持つことができる。オブジェクトにとって特異クラスはスーパークラスとして機能するが、同時にそのオブジェクトの基となったクラスのサブクラスとしても機能する。つまり、親子関係の間に割って入っていくような形になるわけだ。なお、Ruby ではクラスと言えどもオブジェクトであるので、特異クラスを持つことができる。

さて、上のコードを実行したところ次のような結果が得られた(object ID は環境により異なる)。

1. 基底クラス内、全てのメソッドの外側。object ID21625930
2. 基底クラスの特異クラス内。object ID21625920
3. 基底クラスのサブクラス内。object ID21625900
-- 基底クラスのメソッドを直接コール。
4. 基底クラスのクラスメソッド内。object ID21625900
5. sing は特異メソッドを参照。object ID21625840
6. 基底クラスのサブクラスの外。サブクラス AtestObject ID21625900
7. サブクラスの特異クラス内。Object ID21625840
-- サブクラスのインスタンスを作成。
8. インスタンスオブジェクトの Object ID21625620
-- インスタンスメソッドをコール
9. 基底クラスのインスタンスメソッド内。object ID21625620
10. インスタンスオブジェクトの特異クラス内。Object ID21625580

では、1から10の各ステップについて見ていくことにしよう。

Ruby プロセッサがまず出会うのは Test クラスの定義である。ここでメモリ上にオブジェクトが1個確保される(ステップ1)。この基底クラス内でメソッド定義以外で次に出会うのは class << self というコードだが、まだクラスメソッドが一度も定義されていないため、新たに Test クラスの特異クラスが生成される。この特異メソッドの内部で、object IDを出力し(ステップ2)、クラスメソッド meth2 を定義する。

次にRubyプロセッサは class Atest < Test というコードに出会う。そうすると Test クラスのサブクラスである Atest のオブジェクトが生成される(ステップ3)。このオブジェクト内部では Atest.meth2 のようにして基底クラス内で定義されたクラスメソッドを呼び出すことができる(ステップ4)。ただし今いるのは あくまで Atest の中であり、そのため sing = class << self; self; end; の実行で生成されるのは Atest クラスの特異クラスである(ステップ5)。

特異クラスは、例の Active Record の設計においても非常に重要な役割を果たす。Active Record におけるクラスは ActiveRecord::Base のサブクラスであるにもかかわらず、例えば Person クラスには姓名を、 Car クラスには車種を、といったようにクラスの属性を自由に設定できる。これを可能にしているのが他ならぬ特異クラスであり、特異クラスにメソッドを定義することで、基底クラスのメソッドをオーバーライト(overwrite)することなく動的にオーバーライド(override)できる。

次に Atest オブジェクト自身にobject IDを問い合わせてみる(ステップ6)。また、 class << Atest によってその特異クラスを生成する(ステップ7)。さらに Atestnew メソッドを呼び出して実際のインスタンスを作成し、object IDを尋ねる(ステップ8)。オブジェクト a のインスタンスメソッド meth を呼び出すと、予想通りの出力が返ってくる(ステップ9)。最後にオブジェクトaの特異クラスを生成する class << a というコードを実行し、プロセスを終了する。

このように、基底クラスからそのサブクラスのインスタンスまでには6つのレベルが存在している。まず基底クラスがあり、その言わば「影」として特異クラスがある。いわゆるクラスメソッドは特異クラスにおいて定義されたメソッドである。上の例でも、サブクラスである AtestTest クラスを継承しつつも独自のクラスメソッドを持っている。さらに Atest クラスのインスタンスにも特異クラスは設定可能で、これにより、そのインスタンスに固有のメソッドを定義できる。

Rubyの動的な特性について納得はいっただろうか?




大学関係


計算機プログラミング


その他いろいろ


--