「動的な性質」ってよく分からずに読み進めたら、結局リフレクションのことなんですね。
…ある意味、専門領域です。
ということで、3章前半はオープンクラスの話だと思ってしまおう。
3.1節 メソッドの定義を動的に変更
例として、BlankSlateというクラスが挙げられています。ちょっと背景説明の方へ話を移すと、Ruby 1.9系では、言語組み込みとして BasicObject という「何の機能(メソッド)も持たないクラス」が提供されています。
「何の機能(メソッド)も持たないクラス」は method_missing などで動的なメソッド生成をするさいに衝突の危険を避けることが簡単なので重宝されます。
そういった便利なクラスが Ruby 1.8系では言語要素ではないので、Jim Weirich が BlankSlate を提供してくれています。
BlankSlate がやっていることを自然言語で説明すると、「そのクラスに定義されたメソッドを(一部を除いて)一旦 BlankSlate#hidden_method というハッシュに退避した上で undef_method している」です。
こうすることでメソッドの定義されていないクラスを作っています。
また、一旦退避(undef_method)させたメソッドを define_method で再定義(書き戻す)もできます。
これらの undef_method や define_method は動的にメソッド定義を行うための Ruby の仕組みで、メソッド(の定義?body?)自体がオブジェクトであることを利用しています。
3.2節 柔軟なインターフェース
…同じタイトルが2章でもでてきます。Rubyにとって非常に重要なテーマなのでしょう…。3.2.1 instance_eval()
2節でも似たようなテーマでコードブロックの話が出ていますが、ここでもコードブロックの話で、コードブロックに引数を渡す方法とその効用についての話…だと思います。ちょっと何の話を目的にしているのか自信がありません…。
まずは、本に載っているサンプルプログラム(に近いもの)を実際に試してみます。
[kazu@cent6 bestpractice]$ ruby 03-2-1-instanceeval-err.rbinstance_eval() で実行されるコードブロックは、そのオブジェクトをコンテキスト(= enclosing scope)とするので、そのローカル変数しか参照することができません。一方、コードブロックを call メソッドで呼び出す場合、そのコードブロックが定義された時点でのコンテキストを引き継いだ Proc オブジェクトの呼び出しになります。
03-2-1-instanceeval-err.rb:29:in `generate_pdf': undefined local variable or method `full_name' for #<Document:0xb77d0e30> (NameError)
from 03-2-1-instanceeval-err.rb:12:in `instance_eval'
from 03-2-1-instanceeval-err.rb:12:in `generate'
from 03-2-1-instanceeval-err.rb:28:in `generate_pdf'
from 03-2-1-instanceeval-err.rb:35
つまり、
class Prawn::Documentというプログラムでは、
def self.generate(file, *args, &block)
pdf = Prawn::Document.new(*args)
block.arity < 1 ? pdf.instance_eval(&block) : block.call(pdf)
...
end
end
- instance_eval(&block) は pdf オブジェクト(Documentクラス)のコンテキストで
- block.call(pdf) は、blockの本体が作られた時点(MyBestFriendクラスのメソッド呼び出し時)のコンテキストで
3.2.2 method_missing() と send()
Ruby では定義されていないメソッドを実行すると(さっき試したみたいに)「undefined ...」と当然エラーになりますが、method_missing() というメソッドを定義しておくと undefined methods を実行した際に、エラー処理の代わりに method_missing() が実行されます。これを使って動的にメソッドを解釈させる仕組みの説明です。
Convention Over Configuration の例にもなっていると思います。
例として図形描画プログラムが挙げてあります。
複数の図形の描き方(線画、塗りつぶし、線画と塗りつぶしの両方、…)と複数の図形要素(直線、円、多角形…)があるときに、
stroke_line [0,0], [50,50]という書き方が出きるとスマートです。ただ、いちいち
fill_circle_at [100,100], :radius => 50
stroke_and_fill_polygon [100, 250], [...], ...
def 描き方_図形要素(*args)というメソッド定義を行うのは手間がかかります。組み合わせ爆発の状態です。
図形要素(*args)
描き方
end
これを method_missing() を使って、「"描き方_図形要素" というメソッドが来たら、"図形要素"メソッドと"描き方"メソッドを実行する」という仕組みにしています。
また、この時の「…を実行する」ための方法として send(methodname, *args, &block) を使っています。
3.2.3 DSLっぽいアクセサ
DSL = Domain Specific Language = 領域指定言語、つまり、特定分野の記述を行うための言語のことです。Ruby は、その言語の中で DSL 風に書けることがよく知られていて、一部の人には大人気です。さて。
普通のアクセサは
font_size = 10のようになるのですが、定義をちょっと工夫すると
font_size 10という風に書けて、非常にDSLっぽくなります(CSSなんかでこんな記述ができそうな雰囲気)。
しかも、このメソッドは
font_sizeと引数なしで呼び出すと値を返す reader にもなります。
でも、難しい定義ではなく
def font_size(size = nil)とするだけです。
return @font_size unless size
@font_size = size
end
また、通常のアクセサの代わりもさせておくと、この書き方に慣れない人にとって有意義なので、
alias_method :font_size= :font_sizeとエイリアスを作っておくTipsも紹介されていました。
0 件のコメント:
コメントを投稿