gensym

プログラミングと読書、勉強に関するメモ

メタプログラミングRuby その9 [学習メモ]

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

5.4.1 特異クラスの謎
特異メソッドはオブジェクトにメソッドを定義するが、メソッドはオブジェクトに所属することはできない。
しかし、いずれかの既存のクラスに所属してしまうとオブジェクト特異的とは行かなくなる。
そこで、ruby は特異メソッドに対応した特異クラスというクラスを作り、そこに特異メソッドが所属することになる。
特異クラスは普段隠蔽されており、特別な方法でしか知ることができない。
singleton_class メソッドが一番手軽だ。

name = "Bob"

def name.hello
  puts "hello, #{self}!"
end

name.singleton_class
# => #<Class:#<String:0x007ff61a2cd090>>
name.singleton_class.instance_methods false
# => [:hello]
name.singleton_class.superclass
# => String

普通とは違った表示になる。
特異クラスはインスタンスを1つしか持てず(ここでは name)、オブジェクトに紐付いた特異メソッドが所属する。
また、特異クラスのスーパークラスはオブジェクトのクラスになる。
つまり、メソッド探索においてまずはオブジェクトの特異クラスから探索し、その後にオブジェクトのクラスと移っていくことになる。

5.4.4 特異クラスと継承
特異クラスのスーパークラスはもとのクラスのスーパークラスの特異クラスだ。
なにやらこんがらがりそうだが、整理してみよう。

class C
  def self.hello
    puts "hello"
  end
end

class D < C; end

C.singleton_class
# => #<Class:C>
D.singleton_class
# => #<Class:D>

D.singleton_class.superclass
# => #<Class:C> # C ではない!
C.singleton_class.superclass
# => #<Class:Object>
D.hello
# => hello

なぜこのようにややこしくなっているかというと、こうすることによってサブクラスから暮らすメソッドが呼び出せるようになるからだ。

5.6 メソッドラッパー

組み込みや外部ライブラリのメソッドがブラックボックス的になっているときに、周囲に機能を追加したいときにはどうすればいいのか?
5.6.1 アラウンドエイリアス
すでにあるメソッドにエイリアス(別名)をつけて、新たな機能を追加したメソッドで再定義すればよい。
たとえば String クラスの reverse メソッドに呼び出された回数を追加する機能をつけたいとする。

class String
  alias_method :old_reverse, :reverse

  def reverse
    inc_counter
    # カウンターを増加させるメソッド
    old_reverse
  end
end

"hello".reverse
# => olleh

もとの機能を損なわずに、カウント機能を追加することが出来た。

5.6.2 さらにメソッドラッパー
上のやり方だとエイリアスでクラスの名前空間を汚染してしまう恐れがある。
2章で登場した Refinements を使うことで使用範囲を限定するのが安全な方法かもしれない。

module StringRefine
  refine String do
    def reverse
      super "esrever"
    end
  end
end

using StringRefine
"hello".reverse
# => esrever