0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

知ってるようで知らない!Rubyのオブジェクトモデルについて!

0
Posted at

Rubyのオブジェクトモデルを理解する

概要

この記事は、「メタプログラミングRuby 第2版」第2章「オブジェクトモデル」を読み学習した内容を個人学習用にまとめ直したものです。

この記事では、Rubyのメタプログラミングを理解する上で、前提知識となるオブジェクトモデルの基礎知識について説明しています。
元の書籍はRuby 2.xを取り扱っていますが、執筆時点でのRuby 4.0の知識へアップデートしています。

オープンクラス

Rubyではクラスは常にオープンであり、既存のクラスに後からメソッドを追加できる。

# 標準のStringクラスにメソッドを追加する例
class String
  def to_alphanumeric
    gsub(/[^\w\s]/, '')
  end
end

モンキーパッチ

オープンクラスは強力な機能だが、既存のクラスを変更することで予期しない副作用を引き起こす可能性がある。これは「モンキーパッチ」と呼ばれ、既存のメソッドを上書きしてしまうと、そのクラスに依存している他のコードが正常に動作しなくなる危険性がある。使用する際は慎重に検討する必要がある。

オブジェクトモデルの内部

オブジェクトの構成要素

Rubyのオブジェクトは、インスタンス変数とクラスへのリンクで構成されている。

  • インスタンス変数
class MyClass
  def initialize
    @v = 1
  end
end

obj = MyClass.new
obj.instance_variables  # => [:@v]
  • メソッド

オブジェクトのメソッドはオブジェクトのクラスに存在している(インスタンスメソッド)。

# インスタンスメソッド
String.instance_methods == "abc".methods  # => true

# クラスメソッド
String.methods == "abc".methods  # => false
      🧍 user オブジェクト
      ┌─────────────────┐
       データを持つ箱   
      │─────────────────│
       @name = "Taro"   
       @age  = 20       
      └─────────────────┘
              
              │「メソッドはどこ?」
              
      📘 User クラス(設計図)
      ┌─────────────────┐
       ふるまいを定義  
      │─────────────────│
       name             
       age              
       greet            
      └─────────────────┘

クラスの実態

クラスは Class クラスのオブジェクトである。

"hello".class # => String
String.class # => Class

Java など、他のオブジェクト指向言語と違い、RubyClassクラスのインスタンスは実行時に柔軟に情報を書き込むことができる(オープンクラス)。

また、「オブジェクトのメソッドは、そのクラスのインスタンスメソッド」であるので、クラスのメソッドは、Classクラスのインスタンスメソッドとなる。

# 引数のfalseは継承したメソッドは無視するという意味
Class.instance_methods(false) # => [:allocate, :new, :superclass, :subclasses, :attached_object]

各メソッドの補足

  • allocate: newと違い、initializeを呼び出さずにインスタンスを生成
  • subclasses: 直接のサブクラスの配列を返す(Ruby 3.1で追加)
  • attached_object: シングルトンクラスの対象オブジェクトを返す(Ruby 3.2で追加)

モジュール

ModuleClass のスーパークラスとなる。

Class.superclass # => Module

つまり、Classの実態とはオブジェクトの生成やクラスの階層構造を扱うためのインスタンスメソッド(new, allocate, superclass, subclasses, attached_object)を追加したモジュールである。

モジュール自体は、基本的にはメソッドをまとめただけのもの。

定数

大文字で始まる参照は、クラス名やモジュール名を含めて、全てが定数である。

定数はファイルシステムのようなツリー状に配置されており、モジュールやクラスの名前がディレクトリ、通常の定数がファイルのようになっている。

定数のパスは::で区切る。

module M
  class C
    X = "ある定数"
  end

  C::X # => "ある定数"
end

M::C::X # => "ある定数"

ネームスペースとしてのモジュール

モジュールは定数の名前空間を提供する。これにより、異なるライブラリやモジュール間で同じ名前の定数やクラスを使用しても衝突を避けることができる。例えば、ActiveRecord::BaseMyApp::Baseは異なるクラスとして扱われる。

メソッドの探索と継承チェーン

メソッド探索のルール

メソッドを呼び出すと、Rubyはレシーバのクラスに向かって一歩右へ進み、それから継承チェーンが終わるまで探索を続ける。

                          +-----------------------+
                          |     BasicObject       |  (3) find foo
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |       Object          |  (2) find foo
                          +-----------------------+
                                     ^
                                     | not found
+-----------------------+      +-----------------------+
|         obj           | ---> |        String         |  (1) find foo (start)
|      (instance)       |      +-----------------------+
+-----------------------+

モジュールの挿入

モジュールをincludeした場合、そのクラスの継承チェーンの真上にモジュールが挿入される。

class String
  include Module
end
                          +-----------------------+
                          |     BasicObject       |
                          |   (4) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |       Object          |
                          |   (3) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |   Module (included)   |
                          |   (2) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
+-----------------------+      +-----------------------+
|         obj           | ---> |        String         |
|      (instance)       |      |   (1) find foo        |
+-----------------------+      +-----------------------+

多重include

同じモジュールを複数回includeしても、2回目以降は無視される。

module M1
end

module M2
  include M1  # M2の中でM1をinclude
end

class MyClass
  include M2  # M2をinclude(M1も自動的に含まれる)
  include M1  # M1を再度includeしても無視される
end

MyClass.ancestors  # => [MyClass, M2, M1, Object, Kernel, BasicObject]
                   # M1は一度しか含まれない

モジュールをprependした場合、そのクラスの継承チェーンの真下にモジュールが挿入される。

class String
  prepend Module
end
                          +-----------------------+
                          |     BasicObject       |
                          |   (4) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |       Object          |
                          |   (3) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |        String         |
                          |   (2) find foo        |
                          +-----------------------+
                                     ^
                                     | not found
                          +-----------------------+
                          |   Module (prepended)  |
                          |   (1) find foo        |
                          +-----------------------+
                                     ^
                                     | (lookup starts here)
+-----------------------+            |
|         obj           | -----------+
|      (instance)       |
+-----------------------+

selfとは?

  • オブジェクトがメソッドを呼び出すとき、そのレシーバとなるオブジェクト自身がself
  • モジュールあるいはクラスを定義する時は、そのモジュールがself
  • 全てのインスタンス変数はselfのインスタンス変数
  • レシーバを明示しないメソッド呼び出しはselfのメソッドと見なされる
class MyClass
  puts self        # => MyClass(クラス定義時)

  def my_method
    @v = 1         # selfのインスタンス変数
    another        # self.another と同じ
    puts self      # レシーバオブジェクト
  end

  def another; end
end

obj = MyClass.new
obj.my_method      # => #<MyClass:0x...>

Refinements

Refinementsによって、モンキーパッチと同じようにメソッドをオーバーライドし、その影響範囲をグローバルでなく、usingで呼び出したファイルやモジュールの定義の終わりまでに限定できる。

module StringExtentions
  refine String do
    def reverse
      "esrever"
    end
  end
end

module StringStuff
  using StringExtentions
  "my_string".reverse # => "esrever"
end

"my_string".reverse # => "gnirts_ym"

まとめ

  • Rubyのクラスは常にオープンで、後からメソッドを追加できる
  • オブジェクトはインスタンス変数とクラスへのリンクで構成される
  • クラスもClassクラスのオブジェクト。ClassModuleに3つのメソッドを追加したもの
  • メソッド探索は継承チェーン(ancestors)を辿る
  • includeはクラスの真上、prependは真下にモジュールを挿入
  • selfはメソッドのレシーバオブジェクト自身
  • Refinementsでモンキーパッチの影響範囲を限定できる

参考文献

この記事は以下の情報を参考にして執筆した。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?