RubyのDelegate

 とりあえずRuby的な日記なのでとりあえずRuby的な記事を置いておきます。
 題目は最近知ったばかりのDelegate

 RubyにはMix-Inという「クラスに他のモジュールの機能を持ってくる」という機能がありますが、
Delegateの場合は乱暴に言えば「クラスに他のクラスの機能を持ってくる」な感じ。*1
 なんだか「デリゲート」っていう名前からしてプログラミングって感じで興奮しますっ。

 正確には「持ってくる」ではなくて「委譲」なので、語感だけだとちょっと混乱するかも。
 例えば:

  • 数値クラスを継承して属性を増やしたい
  • かといって数値クラスを拡張するのはありえない選択肢
  • そして数値はimmutableなので initialize メソッドで self = n とかできない
  • そもそも self に代入するのは気持ち悪いしルール違反

 なんてときDelegateが使えます。
 こんな感じ。

require "delegate"

class MyInteger < DelegateClass(Integer)
  attr_accessor :progress
  def initialize(n)
    @progress = 0
    super n
  end

  def complete?
    @progress == self
  end
end

i = MyInteger.new(10)
i == 10 # => true

i.progress = 10
i.complete? # => true

 定義したMyIntegerは見ての通り progress という属性を持つ一瞬存在意義のわからないものですが、*2
生成されたインスタンスは i == 10 というように、まるで数値のように数値と適切(?)に比較できます。
 そして == も MyInteger のメソッドなので、 MyInteger#times や MyInteger#coerce も
適切に委譲されています。

 まずDelegateClassにクラスオブジェクトを渡して、Delegateされた即席クラスを作ります。
ご存知の通り Integer クラスには new メソッドが無いので、仮に MyInteger < Integer とした場合
no method error が返ってくるんですが、Delegateされたこのクラスにはメソッドが委譲されているため
super で親の initialize を呼び出すことができます。結果として、 self == n になるわけです。

 ただ、このインスタンスには MyInteger#superclass メソッドがありません。
 そして MyInteger#kind_of?(Integer) == false なので要注意です。
 Integer を直接継承しているわけではないので当然といえば当然ですね。

 このように使いようによっては便利なDelegateですが、問題は使いどころです。
脚注にあるようにイラストロジックソルバに使おうと思ってこんなクラスを定義してはみたものの、
もっといい実装・デザインがあると思うんですよねー。
 本来継承できないものを継承して拡張している時点で気持ち悪さが否めないし。
 どうしたもんかな。

*1:かなり語弊があるので、適切な解説はRubyistMagazineの 標準添付ライブラリ紹介 【第 6 回】 委譲を参照のこと。

*2:現在作成中のイラストロジックソルバに使おうと思って…。