2011年11月14日月曜日

Rubyベストプラクティス 3章(中盤)

前・後編にしようと思ったら思いの外、内容がボリュームあって読むのに時間がかかってブログの更新も止まりがちです…。
ひとまず細かく分けることにしました。

ということで、今回は 3.3節 の特異メソッドの話のみです。

3.3節 オブジェクトごとの振る舞い

普通のオブジェクト指向プログラミング言語では振る舞いをクラスに結び付けて定義します。
Rubyではクラスだけでなく、(同一クラスの特定の)オブジェクトに振る舞いを結びつけることができます。

個人的にはまだこの機能の使いどころはつかめていません。この機能を頭の片隅に置きながら Ruby プログラムをたくさん書くことでしか体得できないのだろうなぁ、と考えています。
なお、Rubyベストプラクティスには
スタブメソッドの呼び出しが、テストの実行結果全体に影響を及ぼすことがないようにしたいためだ。
とテストケースの実行における例が書いてあります。

irb で見る基本的な特異メソッド

Rubyベストプラクティスの p.79 のサンプルを実行してみました:
irb(main):001:0> class User; end
=> nil
irb(main):002:0> user = User.new
=> #<User:0xb776a770>
irb(main):003:0> def user.logged_in?
irb(main):004:1>   true
irb(main):005:1> end
=> nil
irb(main):006:0> user.logged_in?
=> true
irb(main):007:0> another_user = User.new
=> #<User:0xb775715c>
irb(main):008:0> another_user.logged_in?
NoMethodError: undefined method `logged_in?' for #<User:0xb775715c>
    from (irb):8
user には logged_in? メソッドが定義されていて、別のオブジェクト another_user では logged_in? が使えないのが分かります。

実際に動的に特異メソッドを定義する

プログラムの内部で動的に特異メソッドを定義しようと思うと "define_method(symbolname, &block_body}" を使うのがこれまでの知識ですが、

irb(main):002:0> user = User.new
=> #<User:0xb773177c>
irb(main):003:0> user.define_method(:logged_in?) { true }
NoMethodError: undefined method `define_method' for #<User:0xb773177c>
    from (irb):3
直接だと define_method は使えません。

define_method を行うようにするには
irb(main):004:0> singleton = class << user; self; end
=> #<Class:#<User:0xb773177c>>
irb(main):006:0> singleton.send(:define_method, :logged_in?) { true }
=> #<Proc:0xb7713cb8@(irb):6>
irb(main):007:0> user.logged_in?
=> true
と記述します。
ここで使われているテクニック:
  • 004 の部分( singleton = class << user; self; end )では、「クラスメソッドの定義」と同じ手法が使われています(が、詳しくは後で…)
  • 006 の部分( singleton.send(...) )では、本来 private method である define_method を send メソッドを経由することで回避しています。
これでプログラム内で動的にメソッドを定義することが可能になりました。

Ruby におけるクラスメソッドの定義

Ruby ではクラスメソッドを以下のように定義します:
class A
  class << self
    def message
      "hello, world"
    end
  end
end

A.message
# => "hello, world"
「Ruby の世界では全てがオブジェクト」というのを思い出すと、2行目の "class << self " がクラス A のオブジェクトを拡張して(特異メソッドを定義して)いる、と読むことが…できるそうです。僕にはまだもう少し時間がかかりそう。

なので、irb:004 で行っている class << user; self; end は user オブジェクトを拡張するためのクラス(= user オブジェクトの特異メソッドを定義するための「空間(クラス)」)を生成していることになります。
後はそのオブジェクトに対して define_method してあげればいいわけです。

0 件のコメント:

コメントを投稿