「Rubyベストプラクティス」を読む3
3章の話は、2章と似たような優れたインターフェイスの作り方やRubyのリフレクションが紹介されています。
3章目次
BlankSlate:ステロイドで強化されたBasicObject
BlankSlateというライブラリの紹介。メソッドを公開、非公開する仕組みをもったクラス。
仕組みは、メソッドを非公開にするときは、Module#instance_methodで、UnboundMethodを取得しておいてから、undef_methodでメソッドを削除する。
メソッドを公開するときは、取得しておいたUnboundMethodをModule#define_methodに与えて、メソッドを定義する。
柔軟なインターフェイスを作る
instance_eval()をオプション化する
2章で紹介した、instance_evalを使ってselfを変えるAPIはカッコイイけど、selfが変わることが都合が悪い時がある。
class Document def self.generate(file, &block) d = Document.new d.instance_eval(&block) d.write(file) end def text(arg) # 何かする end end class Foo def bar "bar" end def use_document Document.generate("test.txt") do # selfがDocumentオブジェクトなので、下のFoo#bar呼び出しは使えない text "my document #{bar}" end end end
こういうときは、ブロックに与えられた引数の個数を調べて、instance_evalを使うか、自身のオブジェクトを引数として与えるか
選択するようなコードを書ける。
class Document def self.generate(file, &block) d = Document.new block.arity < 1 ? d.instance_eval(&block) : block.call(d) d.write(file) end end class Foo def use_document Document.generate("test.txt") do |d| d.text "my document #{bar}" end end end
method_missing()とsend()を使ってメッセージを扱う
method_missingでメソッド名を解析して、解析した文字列を、sendでメソッド呼び出しする例。
例えば、fill_and_strokeというメソッド呼び出しをmethod_missingでフックして、fillとstrokeのメソッドを呼び出すとか。
ActiveRecordのfind_by_*** とかと同じような仕組みの話だとおもう。
2つの目的を兼ねたアクセサ
メソッドに引数があれば、セッターとして動作して、引数がなければゲッターとして動作するメソッドの話。
これが必要になるのは、instance_evalを使ったAPIを提供しようとするとき、=の付くセッターでは、ローカル変数とみなされてしまうから。
Document.generate do # selfを指定しなければいけない self.font_size = 10 text "the font size is #{font_size}" end # セッター、ゲッターとして動作するメソッドを作る class Document def font_size(size=nil) return @font_size unless size @font_size = size end end Document.generate do # selfを指定しなくても書ける font_size(10) text "the font size is #{font_size}" end
オブジェクトごとの振る舞いを実装する
特異クラスを使って、オブジェクト固有のメソッドを作る話
user = User.new # 特異クラスの取得 singleton = class << user; self; end # メソッド定義 singleton.__send__(:define_method, :logged_in?){ true }
既存のコードを拡張、変更する
新しい機能を追加する
オープンクラスなので、メソッドを追加で定義できるという話。
エイリアス経由で変更する
メソッドに機能を追加したいとき、Module#alias_methodを使ってメソッドの名前を変えて、追加の機能を実装した同名メソッドを作れる。
オブジェクトごとの変更
オブジェクトをextendして追加の機能を持たせることができる。クラス単位の変更となるModule#alias_methodと違って、こちらの方法はオブジェクトごとの変更になる。
クラスとモジュールをプログラムで作る
Class#newで匿名クラスを作れる。Class#newのブロック内でdefすれば、匿名クラスのインスタンスメソッドを作成できる。
def Mystery(secret) if secret == "chunky" Class.new do def message "You" end end else Class.new do def message "Don't" end end end end class Win < Mystery("chunky") def who_am_i "I am Win" end end class EpicFail < Mystery("smooth ham") def who_am_i "I am teh fail" end end
フックとコールバックを登録する
新しく追加された機能を検出する
メソッド定義をmethod_addedでフックできる。
継承を補足する
継承はinheritedでフック出来る。
Mix-inを補足する
includeはincludedでフック出来る。本でも紹介されているけど、以下のようなincludedの使い方を結構見かける気がする。
module MyModule module ClassMethods def class_method # クラスオブジェクト用のメソッド end end def foo # インスタンス用のメソッド end def self.included(base) base.extend(ClassMethods) end end class Foo include MyModule # includedのおかげで、下のようにextendを書く必要はない # extend MyModule::ClassMethods end
まとめ
章の最後では、この章で紹介されたテクニックを使ったNaiveCampingRoutesという面白いコードが書いてある。