Rubyオブジェクトモデルの全体像 — class, module, オブジェクトの関係を理解する
はじめに — この図が Ruby オブジェクトモデルの全て
Rubyのオブジェクトモデルは、たった1枚の図で表現できます。
.class(俺を作ったのは?)
─────────────────────────→
BasicObject
↑ .superclass
Object
↑ .superclass
BasicObject ────────────.class───→ ┐ Module
↑ .superclass │ ↑ .superclass
Object ────────────.class───→ ├──→ Class ─┘
↑ .superclass │ ↑ │
"hello" ──.class──→ String ────────────.class───→ ┘ └─┘ .class(自分自身!)
↑ .superclass(俺の継承元は?)
-
縦(上向き) =
.superclassで継承元を辿る → クラスごとに違う -
横(右向き) =
.classで「俺を作ったクラス」を辿る → 全員 Class に行き着く
この記事では、この図の各パーツを順番に解説していきます。
この記事で理解できること:
- 横軸(.class)— なぜ全員 Class に行き着くのか
- 縦軸(.superclass)— 継承ツリーの仕組み
- Class の正体 — なぜ Module の子どもなのか
- class と module の使い分け
- ancestors — メソッド探索の仕組み
- 特異クラス(singleton class)の役割
対象読者: Ruby初心者〜中級者
参照文献: 「メタプログラミング Ruby 第2版」Paolo Perrotta 著(オライリー・ジャパン)
1. 横軸を理解する — .class(俺を作ったクラスは?)
全てがオブジェクト
Rubyでは、数値も文字列も配列も、全てがオブジェクトです。
.class を呼ぶと、そのオブジェクトを作ったクラスがわかります。
# 全部オブジェクト。全部 .class で「作り主」がわかる
1.class # => Integer
"hello".class # => String
[1, 2].class # => Array
true.class # => TrueClass
nil.class # => NilClass
他の言語では「ただの数値」である 1 も、Rubyではメソッドを持つオブジェクトです。
1.odd? # => true
1.zero? # => false
-5.abs # => 5
オブジェクトの中身
オブジェクトの中には何が入っているのでしょうか?
答えはシンプルで、2つだけです。
┌─────────────────────────────────┐
│ オブジェクト │
├─────────────────────────────────┤
│ ① インスタンス変数(データ) │
│ @name = "太郎" │
│ @age = 25 │
│ │
│ ② クラスへの参照(振る舞いの設計図)│
│ → Userクラスを見に行く │
└─────────────────────────────────┘
※ 概念モデルとしての説明です。実際には文字列の値やオブジェクトIDなど、内部的なデータも保持しています。
メソッド(振る舞い)はオブジェクト自身ではなく、クラス側に定義されています。
オブジェクトは「自分がどのクラスから生まれたか」を覚えていて、メソッドが呼ばれるとクラスを見に行きます。
class User
def initialize(name, age)
@name = name # ← オブジェクトが持つ(インスタンス変数)
@age = age # ← オブジェクトが持つ
end
def greet # ← クラスが持つ(メソッド)
"こんにちは、#{@name}です"
end
end
user = User.new("太郎", 25)
user.greet # => "こんにちは、太郎です"
これが冒頭の図の**横矢印(.class)**の意味です。user.class は User を返し、"hello".class は String を返します。
クラスもオブジェクト
ここがRubyの面白いところです。クラス自体もオブジェクトです。
ということは、クラスにも .class が呼べます。
String.class # => Class
Array.class # => Class
User.class # => Class
つまり String や User は、Class というクラスから生まれたオブジェクトです。
冒頭の図をもう一度見てください。左側の全てのクラスから右に向かって .class の矢印が伸び、全て Class に行き着いています。
全てのクラスの .class は Class
これは継承の頂点である BasicObject ですら例外ではありません。
String.class # => Class
Object.class # => Class
BasicObject.class # => Class ← 継承の頂点ですら!
では Class.class は?
Class.class # => Class (自分自身!)
ここでループが閉じます。図の右下で Class の .class が自分自身を指しているのがこれです。
これがRubyのオブジェクトモデルの横軸の全てです。
2. 縦軸を理解する — .superclass(俺の継承元は?)
.class と .superclass は全く別の質問
横軸(.class)は「俺を作ったのは誰?」という質問でした。
縦軸(.superclass)は「俺の継承元は誰?」という全く別の質問です。
# 同じ String でも、答えが全く違う
String.class # => Class ← 俺を作ったのは?
String.superclass # => Object ← 俺の継承元は?
.class は全てのクラスで答えが同じ(Class)でしたが、.superclass はクラスごとに答えが違います。
superclass で辿る継承ツリー
全てのクラスには親クラス(superclass)があります。
String.superclass # => Object
Object.superclass # => BasicObject
BasicObject.superclass # => nil(ここが頂点)
継承ツリー:
BasicObject ← 全ての頂点(ほぼ何も持たない)
↑
Object ← ほとんどのクラスの親(to_s, == 等)
↑
String / Array / User / ... ← 普段使うクラス
自分のクラスも同じです。
class Animal; end
class Dog < Animal; end
Dog.superclass # => Animal
Animal.superclass # => Object
Object.superclass # => BasicObject
BasicObject
↑
Object
↑
Animal
↑
Dog
冒頭の図の左側、縦に積まれた BasicObject → Object → String がこれにあたります。
3. Class の正体 — Module を継承している
Rubyには class の他に module という仕組みがあります。module はメソッドをまとめて include でクラスに取り込んだり、名前空間を作ったりするための仕組みです。
module Greetable
def greet
"こんにちは"
end
end
class User
include Greetable
end
User.new.greet # => "こんにちは"
ここで面白い事実があります。Class の親クラスを辿ってみましょう。
Class.superclass # => Module
Module.superclass # => Object
Class ──superclass──→ Module ──superclass──→ Object ──superclass──→ BasicObject
│
│ つまり class は module の全機能を持っている
│ + .new でインスタンスを生成できる
└──→ class = module + new
冒頭の図の右側を見てください。Class の上に Module があり、さらに Object → BasicObject と続いています。
class は module を継承して .new の能力を追加したものです。
class と module は赤の他人ではなく、親子関係にあります。
厳密にはクラスは module の全機能をそのまま受け継いでいるわけではありません。
例えばクラスは他のクラスに include できず、refine も使えません。
「module の主要機能 + .new」くらいが正確な表現です。
4. class と module の使い分け
違いを比較
┌──────────────────┬──────────────────┬──────────────────┐
│ │ class │ module │
├──────────────────┼──────────────────┼──────────────────┤
│ インスタンス生成 │ ○ .new できる │ ✕ できない │
│ 継承 │ ○ < で単一継承 │ ✕ 継承できない │
│ Mix-in │ ✕ │ ○ include/prepend │
│ 用途 │ 「何であるか」 │ 「何ができるか」 │
└──────────────────┴──────────────────┴──────────────────┘
is-a なら class、can-do なら module
is-a(〜である)なら class、can-do(〜できる)なら module です。
# 犬は動物「である」→ class の継承
class Animal
def breathe
"呼吸する"
end
end
class Dog < Animal
def bark
"ワン!"
end
end
# 泳ぐ「ことができる」→ module の Mix-in
module Swimmable
def swim
"泳ぐ"
end
end
class Dog < Animal
include Swimmable # 泳げる能力を付与
end
dog = Dog.new
dog.breathe # => "呼吸する" ← Animal から継承
dog.bark # => "ワン!" ← Dog 自身
dog.swim # => "泳ぐ" ← Swimmable から Mix-in
Rubyの標準ライブラリでの実例
# Comparable — 比較「できる」能力を付与
class Weight
include Comparable
attr_accessor :kg
def initialize(kg)
@kg = kg
end
def <=>(other) # これを定義するだけで
@kg <=> other.kg
end
end
a = Weight.new(60)
b = Weight.new(80)
a < b # => true ← Comparable が <, >, == 等を自動提供
a.between?(Weight.new(50), Weight.new(70)) # => true
Comparable module が提供するメソッド:
<=> を定義するだけで、以下が全部使える
┌─────────────────────┐
│ < > <= >= == │
│ between? clamp │
└─────────────────────┘
5. ancestors — メソッド探索の仕組み
ancestors — メソッド探索パスの全体像
superclass は親クラスだけを辿りますが、ancestors は module も含めた完全な探索パスを返します。
Dog.ancestors
# => [Dog, Swimmable, Animal, Object, Kernel, BasicObject]
Dog の ancestors(メソッド探索パス):
Dog → Swimmable → Animal → Object → Kernel → BasicObject
① ② ③ ④ ⑤ ⑥
メソッドが呼ばれると、①から順に探す。
見つかった時点で実行。見つからなければ次へ。
Kernel は Object に include されている module で、puts や require 等の「どこでも使えるメソッド」を提供しています。
Kernel.instance_method(:puts) # => #<UnboundMethod: Kernel#puts>
Kernel.instance_method(:require) # => #<UnboundMethod: Kernel#require>
include と prepend の違い
module の取り込み方で、ancestors の挿入位置が変わります。
module Logging
def greet
puts "ログ: greet が呼ばれた"
super # ← 元のメソッドを呼ぶ
end
end
class User
include Logging # Logging は User の「後ろ」に入る
end
class Admin
prepend Logging # Logging は Admin の「前」に入る
end
include の場合:
User → Logging → Object → ...
↑ User のメソッドが先に見つかる(Logging は呼ばれない)
prepend の場合:
Logging → Admin → Object → ...
↑ Logging のメソッドが先に見つかる(元のメソッドをラップできる)
User.ancestors # => [User, Logging, Object, ...]
Admin.ancestors # => [Logging, Admin, Object, ...]
prepend はメソッドのラップに使うのが典型的なパターンです。
module Benchmark
def heavy_process
start = Time.now
result = super # ← 元のメソッドを実行
puts "実行時間: #{Time.now - start}秒"
result
end
end
class Report
prepend Benchmark
def heavy_process
sleep(1)
"完了"
end
end
Report.new.heavy_process
# ログ: 実行時間: 1.001秒
# => "完了"
Ruby がメソッドを見つけるまで
メソッドが呼び出されたとき、Ruby は以下の手順で探します。
dog = Dog.new
dog.swim # ← これが呼ばれたとき、何が起きるか?
Step 1: レシーバ(dog)のクラスを取得
dog.class → Dog
Step 2: Dog の ancestors を上から順に探す
Dog に swim ある? → ない
↓
Swimmable に swim ある? → ある! → 実行
↓(もし無ければ続く)
Animal → Object → Kernel → BasicObject
Step 3: BasicObject まで見つからなければ method_missing を探す
(同じ経路をもう一度辿る)
Step 4: method_missing も無ければ NoMethodError
dog.swim の探索:
dog ─── class ───→ Dog swim ある? → No
│
↓
Swimmable swim ある? → Yes! ★実行
│
↓
Animal (ここには来ない)
│
↓
Object
│
↓
BasicObject
method_missing — 最後の砦
全ての ancestors を探しても見つからなかった場合、Ruby は method_missing を呼びます。
class FlexibleUser
def method_missing(method_name, *args)
if method_name.to_s.start_with?("say_")
word = method_name.to_s.sub("say_", "")
"#{word}!"
else
super # 本当に存在しないメソッドは親に任せる(NoMethodError)
end
end
end
user = FlexibleUser.new
user.say_hello # => "hello!"
user.say_ruby # => "ruby!"
user.unknown # => NoMethodError
user.say_hello の探索:
FlexibleUser → Object → Kernel → BasicObject
全部なし!
method_missing を探す:
FlexibleUser → method_missing ある! → 実行
method_missing は強力ですが、デバッグが困難になるため乱用は避けましょう。
respond_to_missing? も合わせて定義するのがマナーです。
6. 特異クラス(singleton class)
クラスメソッドの謎
Ruby でクラスメソッドを定義する時、こう書きます。
class User
def self.count
42
end
end
User.count # => 42
この self.count はどこに住んでいるのでしょうか?
User.instance_methods には含まれません。
User.instance_methods(false) # => [:greet] ← count はない
答えは**特異クラス(singleton class)**です。
特異クラスとは
全てのオブジェクトは、自分だけの隠れたクラスを持っています。それが特異クラスです。
User.singleton_class # => #<Class:User>
通常の理解:
User は Class のインスタンス
本当の構造:
User → #<Class:User>(特異クラス) → Class
↑
ここに self.count が住んでいる
図解:
user(オブジェクト)
│
│ class
↓
User(クラス)
│
│ class
↓
#<Class:User>(User の特異クラス)
│ ├── count メソッド ← ここにいた!
│ superclass
↓
#<Class:Object>(Object の特異クラス)
│
↓
Class
特定のオブジェクトだけにメソッドを追加
特異クラスを使うと、特定のオブジェクト1個だけにメソッドを追加できます。
alice = User.new("Alice", 20)
bob = User.new("Bob", 25)
# alice だけに特別なメソッドを追加
def alice.admin?
true
end
alice.admin? # => true
bob.admin? # => NoMethodError(bob には無い)
alice の構造:
alice → #<Class:alice>(alice の特異クラス)→ User → Object
├── admin? メソッド
│
alice だけが持つメソッド
bob の構造:
bob → User → Object
(admin? は無い)
まとめ
最後に、冒頭の図をもう一度見てみましょう。ここまで読んだあなたなら、全てのパーツが理解できるはずです。
.class(俺を作ったのは?)
─────────────────────────→
BasicObject
↑ .superclass
Object
↑ .superclass
BasicObject ────────────.class───→ ┐ Module
↑ .superclass │ ↑ .superclass
Object ────────────.class───→ ├──→ Class ─┘
↑ .superclass │ ↑ │
"hello" ──.class──→ String ────────────.class───→ ┘ └─┘ .class(自分自身!)
↑ .superclass(俺の継承元は?)
| 概念 | 一言で |
|---|---|
| オブジェクト | インスタンス変数 + クラスへの参照 |
| クラス | メソッドの置き場。クラス自体も Class のインスタンス |
| module | 能力を付与する仕組み。include/prepend で取り込む |
| .class | 「俺を作ったのは?」→ 全員 Class に行き着く |
| .superclass | 「俺の継承元は?」→ クラスごとに違う |
| ancestors | メソッド探索の順序。include/prepend で変わる |
| 特異クラス | オブジェクト固有の隠れたクラス。クラスメソッドの住処 |
これらを理解していると、Rubyのコードリーディングやデバッグの精度が格段に上がります。
ancestors を見ればメソッドがどこから来ているか一目瞭然ですし、method(:xxx).owner で定義元を特定できます。
# このメソッド、どこで定義されてる?
User.new("太郎", 25).method(:greet).owner # => User
[1,2,3].method(:map).owner # => Array
1.method(:zero?).owner # => Integer
参照文献: 「メタプログラミング Ruby 第2版」Paolo Perrotta 著(オライリー・ジャパン)