Rubyのクラスはオブジェクトの母、モジュールはベビーシッター
Rubyのクラス、モジュール、オブジェクトの関係を
自分なりにまとめてみた
読みづらいかもしれないけど
だれかの参考になればうれしい
オブジェクトの種類
Rubyはオブジェクト指向言語であり
Ruby空間に存在するオブジェクトをその操作対象とする
Ruby空間には3種類のオブジェクトすなわち
インスタンスオブジェクト、クラスオブジェクト
そしてモジュールオブジェクトが存在している
これらは通常単に
オブジェクト、クラス、モジュールと呼ばれているけど
ここではそれらのオブジェクトとしての側面を強調したいので
あえてその名称を使おう
クラスオブジェクト 〜クラスとしての側面〜
クラスオブジェクトは通常単にクラスと呼ばれ
主にRuby空間に
インスタンスオブジェクトを生み出すために存在する
生み出されるインスタンスオブジェクトのデザインは
クラスオブジェクトに記述されており
しかもユーザがインスタンスオブジェクトにアクセスして
その機能を実現しようとするとき
インスタンスオブジェクトは
クラスオブジェクトからその機能を借り出す
Rubyにはその設計者により
予め多数のクラスオブジェクトが用意されている
これらは組み込みクラスと呼ばれる
ユーザは組み込みクラスを自由に使うことができるけれど
class式を使って独自クラスを定義することもできる
class Creature def initialize(name) @name = name end end
ユーザがclass式を使って
既存クラスと同名のクラスオブジェクトを定義した場合
それは既存クラスの書き換えではなく拡張となる
その既存クラスが本来持っている機能は失われず
新たな機能がそこに付加される
class String def speak(word) puts word end end my_name = "Charlie" my_name.speak('Hello') # => Hello my_name.length # => 7
もっとも同名のメソッドを再定義すれば
それは基のメソッドの上書きになるので注意を要する
class String def length "I don't wanna tell you." end end "Charlie".length # => "I don't wanna tell you."
特定のクラスオブジェクトから
インスタンスオブジェクトを生成するには
newメソッドを使う
class Creature def initialize(name) @name = name end end # Creatureクラスのnewメソッドでオブジェクトを生成する my_pet = Creature.new('Doggie')
ただ代表的な組み込みクラスでは
リテラル表記を使って
簡易にインスタンスオブジェクトを生成できる
# 文字列オブジェクトの生成 my_name = "Charlie" # 整数オブジェクトの生成 my_age = 195 # 配列オブジェクトの生成 my_pets = [ 'Dog', 'Crocodile', 'Hippopotamus' ] # ハッシュオブジェクトの生成 my_favorite = { :number => 3, :language => 'Ruby', :color => 'Blue' } # 範囲オブジェクトの生成 my_range = 9..21 # 正規表現オブジェクトの生成 my_regexp = /ruby/
オブジェクトの特性は
そのクラスオブジェクトのメソッド定義でほぼ決まる
メソッドはdef式を使って定義できる
class Creature def self.description "I'm a Creature Class for making creatures." end def initialize(name) @name = name end def name @name end end
クラスオブジェクトには
インスタンスオブジェクトのためのInstanceメソッドと
自身のためのselfメソッドとを定義できる
selfメソッドはメソッド名の前に
selfあるいはクラス名を冠することで
Instanceメソッドと区別される
クラスオブジェクトにおけるselfメソッドは
普通クラスメソッドと呼ばれている
Creature.description # => "I'm a Creature Class for making creatures."
Instanceメソッドはこのクラスオブジェクトから
派生するインスタンスオブジェクトの挙動を決定付ける
つまりクラスオブジェクトから
インスタンスオブジェクトが生成されたとき
Instanceメソッドがあたかも
インスタンスオブジェクト自身が持つメソッドのように振る舞う
my_pet = Creature.new('Doggie') my_pet.name # => "Doggie"
クラスオブジェクト 〜オブジェクトとしての側面〜
確かにクラスオブジェクトは
インスタンスオブジェクトを生成するための雛形的なものだ
だけれども同時にクラスオブジェクトは
それ自身もRuby空間に存在するオブジェクトである
インスタンスオブジェクトに
クラスオブジェクトという母があるように
すべてのクラスオブジェクトにも
Classクラスオブジェクトという母がある
つまりすべてのクラスの雛形となっているのは
Classクラスオブジェクトであり
クラスオブジェクトはすべてここから生成されている
classメソッドでこの事実を知ることができる
Object.class # => Class Array.class # => Class Binding.class # => Class Continuation.class # => Class Data.class # => Class Exception.class # => Class Dir.class # => Class File::Stat.class # => Class Hash.class # => Class IO.class # => Class File.class # => Class MatchData.class # => Class Method.class # => Class Module.class # => Class Numeric.class # => Class Proc.class # => Class Process::Status.class # => Class Range.class # => Class Regexp.class # => Class String.class # => Class Struct.class # => Class Symbol.class # => Class Thread.class # => Class ThreadGroup.class # => Class Time.class # => Class UnboundMethod.class # => Class TrueClass.class # => Class FalseClass.class # => Class NilClass.class # => Class
驚くべきことに
Classクラスオブジェクトの母も
Classクラスオブジェクト自身である!
Class.class # => Class
あなたが後から作るクラスオブジェクトも
その母はあなたではなくClassクラスオブジェクトである
Creature.class # => Class
またある特定のクラスのサブクラスも
その母はスーパークラスではなく
Classクラスオブジェクトである
class Person < Creature # CreatureクラスのサブクラスPersonを定義 end Person.class # => Class
兎にも角にも
あらゆるクラスオブジェクトは
一つのクラスオブジェクトClassから生成されているのだ!
つまりRuby空間には
最初にClassクラスオブジェクトから生成された
Classクラスオブジェクトがあり
そのClassクラスオブジェクトが
次いで他のすべてのクラスオブジェクトを生成し
最後にこの生成された各種のクラスオブジェクトから
インスタンスオブジェクトが生成される
という構図が描かれる
クラスオブジェクトから
インスタンスオブジェクトを生成するときは
newメソッドを使うが
クラスオブジェクトの生成にはその必要はない
規定のクラスオブジェクトについてはおそらく初期化時に
ユーザ定義のクラスオブジェクトについてはclass定義式の解析時に
Rubyが自動で生成する
生成されたクラスオブジェクトには
そのクラス名を冠した定数が付けられ
これによりユーザによる
クラスオブジェクトへのアクセスが可能になる
このことを確認するために
恣意的にClassクラスオブジェクトのnewクラスメソッドを使って
クラスオブジェクトを生成してみよう
puts Creature = Class.new(Creature) # =>warning: already initialized constant Creature # => Creature
これによりCreature定数には既に
Creatureクラスオブジェクトが
セットされていることが確認できる
なお上記により
Creatureクラスオブジェクトのサブクラスが生成され
それがCreature定数に再設定される
既に書いたがクラスオブジェクトには
自身のためのselfメソッド(クラスメソッド)を定義できる
クラスオブジェクトに対し
クラスメソッドを直接呼び出すことによって
クラスオブジェクト自身にアクセスできる
クラスメソッドは
そこから派生したインスタンスオブジェクト全体を
管理するためなどに使うことができる
class Creature @@counter = 0 def initialize(name) @name = name @@counter += 1 end def self.count "You have #{@@counter} creatures." end end dog = Creature.new('hot') alligator = Creature.new('thanks') hippopotamus = Creature.new('idiot') Creature.count # => "You have 3 creatures."
継承(Inheritance)
継承とはクラスオブジェクト間の相互依存関係のことである
Rubyではあるクラスオブジェクトが定義したメソッドを
あたかも自分に定義されたもののように
他のクラスオブジェクトが利用できる
利用される側をスーパークラス
利用する側をサブクラスと呼ぶ
他のクラスオブジェクトを利用して
クラスを定義する場合
自分の名前にスーパークラス名を接ぎ木する
class Person < Creature # CreatureクラスのサブクラスPersonを定義 def initialize(name,age) super(name) @age = age end def age @age end end me = Person.new('Charlie', 8) me.name # => "Charlie" me.age # => 8
こうすれば
サブクラスPersonのインスタンスであるmeオブジェクトでも
自ら定義することなくメソッドnameが使える
つまりme.nameが実行されたとき
このメッセージは最初Personクラスオブジェクトに送られて
そこで対応するnameメソッドが存在しないことが分かると
次いでそのスーパークラスに渡され実行される
(Moduleクラスオブジェクトの話はここでは割愛する)
一般的に言えば
Rubyはメッセージに対応するメソッドが見つかるまで
クラスツリーを遡り
最後にはObjectクラスオブジェクトに至る
一つのクラスオブジェクトは同時並行的に
複数のクラスオブジェクトと継承関係になれない
つまり複数のスーパークラスを同時に持てない
このような制限を
制限のない多重継承に対して単純継承という
しかし他のクラスオブジェクトのサブクラスを
スーパークラスにすることはできる
この数つまり経時直線的な段数に制限はない
class PersonInEarth < Person def initialize(name, age, country) super(name, age) @country = country end def country @country end end a_friend = PersonInEarth.new('Fernando', 34, "Spain") a_friend.name # >> "Fernando" a_friend.country # >> "Spain"
誰がスーパークラスかはsuperclassメソッドで調べられる
PersonInEarh.superclass # >> Person
Rubyでは継承関係にない
独立したクラスオブジェクトというのは作れない
クラス定義においてスーパークラスを指定しないとき
Rubyは勝手にObjectクラスオブジェクトを
そのスーパークラスにセットする
つまりすべてのクラスオブジェクトは
Objectクラスオブジェクトのサブクラスである
組み込みクラスも例外ではない
何も定義しないクラスでmethodsメソッドを呼べば
それが既にObjectクラスオブジェクトの
サブクラスになっていることが確認できる
(このメソッドを呼べること自体が証拠ですが)
class Nothing end n = Nothing.new p n.methods # >> ["inspect", "tap", "clone", "public_methods", "object_id", "__send__", "instance_variable_defined?", "equal?", "freeze", "extend", "send", "methods", "hash", "dup", "to_enum", "instance_variables", "eql?", "instance_eval", "id", "singleton_methods", "taint", "frozen?", "instance_variable_get", "enum_for", "instance_of?", "display", "to_a", "method", "type", "instance_exec", "protected_methods", "==", "===", "instance_variable_set", "kind_of?", "respond_to?", "to_s", "class", "__id__", "tainted?", "=~", "private_methods", "untaint", "nil?", "is_a?"]
Nothingクラスオブジェクトは
Objectクラスオブジェクトが持っている
すべてのメソッドを継承する
継承はクラスオブジェクト間の師弟制度のようなものである
とりわけRubyの継承は一子相伝
一人がそのすべてを引き継ぐという特徴を有する
この特徴のため
継承関係が成熟しクラス階層が限りなきものになったとしても
Rubyは迷うことなくその末端から頂点つまり
Objectクラスオブジェクトまでを遡ることができる
基本的にサブクラスは
スーパークラスの特性をすべて引き継ぐが
サブクラスにおいて
その一部を拒否したり再定義することは許される
class PersonInEarth < Person undef :age # ageメソッドを未定義にする alias :name_old :name #nameメソッドをname_oldに変える def initialize(name,age,country) super(name,age) @country = country end def country @country end def name # nameメソッドを再定義する "my name is #{name_old}." end end a_friend = PersonInEarth.new('Fernand', 34, "Spain") p a_friend.name # >> "my name is Fernand." p a_friend.age # ~> -:39: undefined method `age' for #<PersonInEarth:0x23550> (NoMethodError)
モジュールオブジェクト
単純継承はメソッド探索の複雑さを排除する
一方で継承の本来的意義を低下させうる
仮に異なる系譜の継承クラス群があり
その両方の系譜の特性を持った
クラスオブジェクトを生成したい場合
単純継承ではそれを一方の系譜のサブクラスとし
そこに他方の系譜の特性すべてを一から書き足す必要が生じる
これは継承の目的に反し極めて非生産的だ
Rubyではモジュールオブジェクトがこの問題を最小化する
モジュールオブジェクトは通常単にモジュールと呼ばれる
モジュールオブジェクトは
継承関係に立つことができない独立したクラスオブジェクトである
そこからインスタンスオブジェクトを生成することもできない
モジュールオブジェクトはその中に特定の機能のまとまりを持って
クラスオブジェクトにMix-inつまり挿し木される
モジュールオブジェクトをMix-inしたクラスオブジェクトは
追加的にその機能を獲得することになる
module Behavior def self.description # モジュールメソッドの定義 "I'm a Behavior Module." end def sleep # Instanceメソッドの定義 "I'm sleeping." end def eat "I'm eating." end end class PersonInEarth < Person include Behavior # Behaviorモジュールを読み込む def initialize(name,age,country) super(name,age) @country = country end def country @country end end a_friend = PersonInEarth.new('Fernand', 34, "Spain") a_friend.eat # >> "I'm eating." a_friend.sleep # >> "I'm sleeping." Behavior.description # >> "I'm a Behavior Module."
モジュールの定義はmodule式で行う
クラスオブジェクトと同様モジュールオブジェクトには
インスタンスオブジェクトのためのInstanceメソッドと
自身のためのselfメソッドとを定義できる
モジュールのselfメソッドは一般にモジュールメソッドと称される
クラスオブジェクトにモジュールオブジェクトを
Mix-inするにはincludeメソッドを使う
これによりあたかも
モジュールオブジェクトで定義したメソッドが
クラスオブジェクトにあるかのように働く
よって
クラスオブジェクトから生成されたインスタンスオブジェクトは
それらのInstanceメソッドを自由に使える
もっともモジュールのselfメソッドが
Mix-in先クラスのselfメソッドとして働くことはない
つまりモジュールメソッドはクラスメソッドにはならない
この点が継承の場合とは異なっている
モジュールオブジェクトのMix-inによって
継承におけるメソッド探索のルートが変わる
モジュールオブジェクトをMix-inした
クラスオブジェクト内が探索されると
そのスーパークラスに先立って
モジュールオブジェクト内が探索される
多重継承におけるようなあいまいさはない
ancestorsメソッドでその順位を確認できる
PersonInEarth.ancestors # >> [PersonInEarth, Behavior, Person, Creature, Object, Kernel, BasicObject]
インスタンスオブジェクトにとって
その母がクラスオブジェクトであるならば
モジュールオブジェクトは
彼のベビーシッターのような存在だ
母に代わって子をヘルプする
ベビーシッターがそうであるように
モジュールオブジェクトは
複数のクラスオブジェクトにおいて掛け持ちされうる
この点に鑑みればモジュールオブジェクトに
特定のインスタンスオブジェクトの属性情報を保持させる
つまりインスタンス変数を持たせることは
危険だということが分かる
なお
モジュールオブジェクトは継承関係には立てないが
モジュールオブジェクトに
他のモジュールオブジェクトをMix-inすることはできる
しかし最終的にモジュールオブジェクトは
クラスオブジェクトにMix-inされ
その継承関係に割り込まなければ機能しない
(ただモジュールメソッドは直接呼ぶことができる)
インスタンスオブジェクト
インスタンスオブジェクトは
普通単にオブジェクトあるいはインスタンスと呼ばれ
先に書いたようにクラスオブジェクトをnewすることで
Ruby空間に生み出される
Ruby空間では
各種のクラスオブジェクトから生み出された
多数のインスタンスオブジェクトが
順次・分岐・繰り返しの制御構造の中で
相互に働き掛けあうことによって
ユーザの所望する意味のある結果が返される
ところがその存在の重みとは裏腹に
インスタンスオブジェクトの中身はほとんど空である
基本的にインスタンスオブジェクトは
自分の属性情報のみを保持する
他のオブジェクトとの相互作用のための
メソッド群を基本的に保持しない
つまりインスタンスオブジェクトは
自分が何者で誰が親なのかということは知っているけれども
ユーザから送られてくるメッセージの処理方法を知らない
一方インスタンスオブジェクトへのアクセスは
それにメッセージを送ることで達成される
より正確にはメッセージを送る以外に
インスタンスオブジェクトにアクセスする手段はない
結局メッセージを受け取ったインスタンスオブジェクトは
それを自分の生成元のクラスオブジェクトに投げ
彼女がインスタンスオブジェクトに代わって答えを用意する
そのクラスオブジェクト自身が
対応するメソッドを備えていない場合
先に書いたように
モジュールオブジェクトを含むクラスツリーを辿って
メソッドが探索される
# a_friendでラベル付けされたオブジェクトにメッセージnameを送る
a_friend.name
この例でa_friendでラベル付けされたインスタンスオブジェクトは
メッセージnameを受け取るとこれを
その生成元であるPersonInEarthクラスオブジェクトへ送る
(後で述べるSingletonメソッドがある場合はまずそれを探索する)
PersonInEarthでは対応するnameメソッドを呼び出すために
まず自分自身がそれを持っているかが調べられる
次いでそこにincludeしたBehaviorモジュールオブジェクト内が探索される
PersonInEarthおよびBehaviorモジュールは
nameメソッドを持っていないので
メッセージは今度は
そのスーパークラスであるPersonに渡される
ところがPersonクラスオブジェクトも
nameメソッドを備えていないので
メッセージは更に
そのスーパークラスであるCreatureクラスに渡される
そしてここに定義されたnameメソッドが実行され
その結果が順次逆のルートを辿って
a_friendでラベル付けされた
インスタンスオブジェクトからユーザに返される
Singletonメソッド(抽象メソッド)
インスタンスオブジェクトの中身は
ほとんど空であるということを書いた
しかしインスタンスオブジェクトは
クラスオブジェクトやモジュールオブジェクトと同様に
その内部にselfメソッドを持つことができる
インスタンスオブジェクトにおけるselfメソッドは
Singletonメソッドまたは抽象メソッドと呼ばれる
Singletonメソッドは
そのインスタンスオブジェクト固有のメソッドを
定義するために使われる
a_friend = PersonInEarth.new('Fernand', 34, "Spain") def a_friend.name "My friend, #{@name}" end p a_friend.name # >> "My friend, Fernand"
メソッド定義におけるメソッド名の前に
インスタンスオブジェクトを置くことによって
そのインスタンスオブジェクトの
Singletonメソッドが定義される
Singletonメソッドはクラスツリーの最下層に位置し
メソッド探索において最優先の探索先となる
正確に記せばSingletonメソッドは
そのインスタンスオブジェクト自身に
定義されているのではなく
そのインスタンスオブジェクトと
そのクラスオブジェクトとの間に生成される
無名のクラスに定義される
だからこの無名クラスにSingletonメソッドを定義しても
同様の結果が得られる
class << a_friend def name "My friend, #{@name}" end end p a_friend.name # >> "My friend, Fernand"
class名を無名とし
インスタンスオブジェクト名を
二重の接ぎ木記号で繋ぐ(感情的には接ぎ木の向きは逆ですが)
複数のSingletonメソッドをまとめて定義する場合
この書式が有用だ
この無名クラスはSingletonクラスとも呼ばれる
Singletonクラスは
クラスメソッドやモジュールメソッドを
定義する場合にも使える
extend
なおSingletonクラスはクラスに他ならないので
当然そこにモジュールオブジェクトをMix-inできる
module Business def job "Programmer" end end class << a_friend include Business end p a_friend.job # >> "Programmer"
SingletonクラスにMix-inされた
モジュールBusinessのメソッドjobは
インスタンスオブジェクトa_friendのSingletonメソッドになる
でもRubyではもっと簡単にモジュールメソッドを
SingletonメソッドとしてMix-inする方法がある
それがextendだ
SingletonメソッドがSingletonクラスのメソッドを
直接インスタンスオブジェクトに追加できるようにするのと同様
extendはモジュールのメソッドを
直接インスタンスオブジェクトに追加できるようにする
a_friend.extend Business p a_friend.job # >> "Programmer"
これによりモジュール内メソッドは
特定のインスタンスオブジェクトの機能になる
まとめ
最後にクラス、モジュールおよびオブジェクト
の特性を整理しておこう
- すべてのクラスオブジェクトは、Classクラスオブジェクトから生成される
- クラスオブジェクトは、インスタンスオブジェクトの雛形となり、それを生み出す母のような存在である
- それと共にそれ自身もオブジェクトである
- クラスオブジェクトは、インスタンスオブジェクトのためのInstanceメソッドと自身のためのクラスメソッドを持てる
- クラスオブジェクトは、継承によって他のクラスオブジェクトのメソッドを利用できる
- すべてのクラスオブジェクトは継承に係わっていて、その頂点にはObjectクラスオブジェクトがいる
- Rubyの継承は、スーパークラスを唯一つしか持たない単純継承である
- しかし継承の経時直線的な段数には制限はない
- モジュールオブジェクトは、クラスオブジェクトに代わってインスタンスオブジェクトを支援する、ベビーシッターのような存在である
- モジュールオブジェクトは継承関係に係われず、インスタンスオブジェクトを生成することもできない
- モジュールオブジェクト自身もオブジェクトであり、Instanceメソッドの他に自身のためのモジュールメソッドを持てる
- インスタンスオブジェクトは、クラスオブジェクトから生成される
- インスタンスオブジェクトがRuby空間における主役である
- インスタンスオブジェクトには、メッセージ送信以外にアクセス方法がない
- インスタンスオブジェクトに送られたメッセージは、クラスツリーに従って順次クラスオブジェクトに渡される
- インスタンスオブジェクト自身も固有のメソッドを持てる
関連記事:
Rubyのシンボルは文字列の皮を被った整数だ! - hp12c
Rubyのブロックはメソッドに対するメソッドのMix-inだ! - hp12c
Rubyのyieldは羊の皮を被ったevalだ! - hp12c
(追記:2008/8/17) メソッド探索の順位について誤りがあったので訂正しました
(追記:2008/8/27) extendの項目を追加しました