September 03, 2005Ruby extend[Computer]
Rubyの話です。 Module A
def hoge; puts hoge; end end class B; extend A; end B::hoge # => "hoge" ようするにBのクラスメソッドとしてhogeという関数を取り込んでいます。 extendの実践的な使い方として、例えば時間がかかる処理を行うClassに対し、キャッシュの機能をもつModuleを書いて、そのModuleをClassに対してextendすることによってキャッシュを効かせるようにしたいということが考えられます。そこで次のようなコードを書いてみました。 class DB1
@@db = {} def DB1.get(key); return @@db[key]; end # 時間がかかる処理 end class DB2 module CachedDB class DB1; extend CachedDB; end DB1::get('hoge') #=> CachedDB::get('hoge') 一見これでよさそうに見えます(少なくとも私にはそう見えました…)が、DB1とDB2の中身でkeyが重なってしまうと上手く動きません。というのもCachedDBのスコープで宣言されているクラス変数@@cacheはあくまでもCachedDBのクラス変数であって、extendされた側のDB1やDB2のクラス変数ではないからです。このことに気づくのに時間がかかりました。 解決法は次のとおりです。CachedDBを次のものに入れ替えます。 module CachedDB
def CachedDB.extend_object(obj) super obj.instance_eval(<<__EVAL_STRING__) @cache = {} def cache; return @cache; end __EVAL_STRING__ end def get(key); return cache[key] || super; end end extend_object(obj)はextendのコールバック関数で、例えばClass Aに対してModule BをextendするとB::extend_object(A)という形で起動されます。このことを利用して、instance_evalのところでextendされた側のクラスのクラス変数としてキャッシュの定義、並びにそのクラス変数へのアクセサを提供しています(アクセサを経由しないと遅延評価にならないため)。これで先ほどの例でいうとDB1とDB2でそれぞれにキャッシュが作られるようになり、keyが重複していても問題なくなります。 コメント
コメントする
|
スポンサード リンク
|