読書ログ『メタプログラミング Ruby 第2版』5章


5章: 木曜日: クラス定義

はじめに

  • Rubyのclassキーワードは、オブジェクトの動作規定だけでなく、実際にコードを実行している
  • この章では、クラスマクロ(クラスを修正する方法) / アラウンドエイリアス(メソッドをコードでラップする方法) / 特異クラス(シングルトンクラス) を説明する

クラス定義とは

  • クラス定義にはあらゆるコードを置くことができる
    • メソッドを定義するだけの場所ではない
class MyClass
    puts 'Hello'
end
  • メソッドやブロックと同じように、クラス定義も最後の命令文の値を戻す
  • クラス(やモジュール)定義の中では、クラスがカレントオブジェクトselfになる
    • クラスとモジュールも単なるオブジェクトにすぎない。
result = class Myclass
    self
end

result #=> MyClass

カレントクラス

  • Rubyのプログラムは、常にカレントオブジェクトselfと カレントクラスを持つ
    • メソッドを定義すると、カレントクラスのインスタンスメソッドに
  • プログラムのトップレベルでは、カレントクラスは、mainのクラスのObjectになる。
  • メソッドの中では、カレントオブジェクトのクラスが、カレントクラスに。

カレントオブジェクト

  • self
  • インスタンスメソッドが呼び出されるレシーバのこと

Module#class_eval

  • クラス名がわからない時にクラスをオープンするために用いる
    • クラスのコンテキストでブロックを評価するもの
def add_method_to(a_class)
    a_class.class_eval do
        def m; 'Hello!'; end
    end
end

add_method_to Strign
"abc".m #=> "Hello!"
"abc".m.class #=> String
  • class_evalは、selfとカレントクラスを変更する
  • カレントクラスを変更することで、クラスを再オープンできる
  • classが定数を必要とするのに対し、class_evalはクラスを参照する変数ならなんでも使える
  • class_evalは、フラットスコープを持つので、class_evalブロックのスコープの外側の変数も参照できる

instance_evalとclass_evalの使い分け

  • クラス以外のオブジェクトをオープンしたいなら、instance_eval
  • クラス定義をオープンして、defを使ってメソッドを定義したいなら、class_eval

カレントクラスのまとめ

  • Rubyのインタプリタは、常にカレントクラスの参照を追跡
    • defで定義された全てのメソッドは、カレントクラスのインスタンスメソッドに。
  • クラス定義の中では、カレントオブジェクトselfとカレントクラス(定義クラス)は同じ
  • クラスへの参照を持っていれば、クラスはclass_evalでオープンできる

クラスインスタンス変数

  • クラスのインスタンス変数と、クラスのオブジェクトのインスタンス変数は別物
  • クラスのスコープで定義した変数のことを「クラスインスタンス変数」
  • メソッドのスコープで定義した変数を「インスタンス変数」

    • 参考 この記事がすごく参考になりました。
  • アクセスできるのはクラスだけであり、クラスのインスタンスやサブクラスからはアクセスできない

class MyClass
    @my_var = 1
    def self.read; @my_var; end
    def write; @my_var = 2; end
    def read; @my_var; end
end

obj = MyClass.new
# インスタンスメソッドでアクセスできない。
obj.read #=> nil
obj.write
# インスタンスメソッドwriteで定義したものを呼び出す
obj.read #=> 2
# クラスメソッドで呼び出せる!
MyClass.read #=> 1 

クラス変数

  • @@プレフィックスをつけたもの
  class C
    @@v = 1
  end
  • クラス変数は、サブクラスや通常のインスタンスメソッドからもアクセスできる
  • クラス変数は、クラスではなく クラス階層に属している
    • ハマることがあるので、Rubyistはクラス変数を使わず、クラスインスタンス変数を推奨

特異メソッド

  • 単一のオブジェクトに特化したメソッド
    • 特定のオブジェクトにメソッドを追加できる
  • Object#define_singleton_methodで定義できる
str = "just a regular string"

def str.title?
    self.upcase == self
end

self.title? #=> false
str.methods.grep(/title?/)  #=> [:title?]
str.singleton_methods               #=> [:title?]

Rubyにおける型

  • 「型」はオブジェクトが反応するメソッドの集合に過ぎない
    • Rubyなどの動的言語では、オブジェクトの「型」はそのクラスとは厳密に結びついていない
  • ダックタイピング
    • 「型」はオブジェクトが反応するメソッドの集合に過ぎないという、流動的な型のこと。

クラスメソッドの真実

  • クラスは単なるオブジェクトであり、クラス名は単なる定数
  • クラスメソッドは、クラスの特異メソッド

クラスマクロ

  • クラス定義の中で使えるクラスメソッド
    • ex) attr_accessor

特異クラス

  • オブジェクトの通常クラスとは別に持つ、特別なクラスのこと
    • メタクラスやシングルトンクラスとも
  • 特異クラスは、オブジェクトの特異メソッドが住んでいる場所
    • Object#singleton_class か、 class << 構文 によってみることができる
  • 特異クラスは、インスタンスを一つしか持てない
    • だから、 シングルトンクラス
  • オブジェクトが特異メソッドを持っていると、Rubyは通常のクラスではなく、特異クラスのメソッドから探索を始める。

Rubyのオブジェクトモデルの7つのルール

  • オブジェクトは1種類しかない
    • それが、通常のオブジェクトかモジュールに
  • モジュールは1種類しかない
    • それが通常のモジュール、クラス、特異クラスのいずれかに
  • メソッドは1種類しかない
    • メソッドはモジュール(大半はクラス)に住んでいる
  • 全てのオブジェクト(クラス含む)は、「本物のクラス」を持つ
    • それが通常のクラスか特異クラス
  • 全てのクラス(BasicObjectを除く)は、一つの祖先を持つ
    • あらゆるクラスがBasicObjectに向かって、1本の継承チェーンをもつ。
  • オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラス
    • クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラス
  • メソッドを呼び出すときは、Rubyはレシーバの本物のクラスに向かって、「右へ」煤sみ、継承チェーンを「上へ」進む。

クラスメソッドの構文

  • self.メソッドを使う場合
class MyClass
    def self.another_class_method; end
end
  • << self を使う場合
class MyClass
    class << self
        def yet_another_class_method; end
    end
end

メソッドラッパー

アラウンドエイリアス

  • Module#alias_methodを使って、Rubyのメソッドにエイリアスをつける
    • alias_methodを使うときは、メソッドの新しい名前を先に、元の名前を後に
class myClass
    def my_method; 'my_method()'; end
    alias_method :m, :my_method
end

obj = MyClass.new
obj.my_method   #=> "my_method()"
obj.m                   #=> "my_method()"

メソッドの再定義

  • 新しいメソッドを定義して、元の名前をつけること
    • 元のメソッドを変更することではない

アラウンドエイリアスをまとめると

  • メソッドにエイリアスをつける
  • メソッドを再定義する
  • 新しいメソッドから古いメソッドを呼び出す。

感想

む、、、むずい。なんとか食いついたという感覚

時間を置いてもう一度読み直す。