Pythonのデコレータがとっても便利です。すごくステキ。シンプルな文法のシンタックスシュガーなのに、夢がすごく広がります。僕がデコレータを本格的にさわったきっかけは、TurboGears。ビューのクラスのメソッドに、@exposeとやると、そのメソッドがルーティングテーブルに登録されて、ブラウザからそのメソッド名のURLをたたくと、外から呼び出せるようになる、という仕掛けです。実際に、内部で行われている処理はこんな感じです。
#pythonのデコレータのサンプル @expose def exposed_method(): pass #こうやって解釈される def expsed_method(): pass exposed_method = expose(exposed_method)
PySpecではテストメソッドの宣言をしたり、wxPythonのユーティリティ関数でイベントハンドラを定義したり(@expose(wx.EVT_BUTTON, "EXIT_BUTTON)みたいな感じで定義できるようにしている)、メソッドに新しい情報を付加したり、関数をラップして処理を割りこませたり、もう、これなしでは設計できないぐらい、いつも便利に使っています。
Rubyにも、デコレータ的なメソッドがあります。publicとかですね。publicと書いたら、その後に定義されたメソッドが全部publicになるという。きっと、「これから登録されるメソッドの公開度はこうしますよ」という情報がクラスの中にあって、それが設定されるような感じかな、と思っています。というわけで、このpublicみたいに、クラス宣言の中で使えて、メソッドに情報を付加するという、デコレータ的な仕組みを実装してみました。サンプルコードとしてはこんな感じです。
class TargetClass include DecoratorEnabler # includeするとデコレータ"expose"が使用可能に # これは公開しないメソッド def not_exposed_method end expose # これは公開するメソッド def exposed_method p "exposed_method()" end end
実現にあたっては、twitter上で、@itawasaさん、@shimizukawaさん, @ikasamtさんから参考情報などを教えていただきました。ありがとうございます。
サンプルコードでは使っていませんが、publicとかと違い、メソッドの呼び出し時にパラメータも渡せるようになっています。それでは実装を公開します。
#デコレータを管理するモジュール #対象となるクラスごとにデコレータの記録を付ける module Decorators @@__decorators = Hash.new {|hash, key| hash[key] = []} # デコレータが使用されたら呼ばれるメソッド。次に登録されたメソッドと結びつけられる def self.new_decorator(klass, key, args) decorators = @@__decorators[klass] decorators.pop if decorators.last == nil decorators.push(Decorator.new(key, args)) end # メソッドが定義されたら呼ばれるメソッド def self.regist_method_to_last_decorator(klass, method) decorators = @@__decorators[klass] if decorators.last != nil decorators.last.regist_method(method) # 連続登録を防止するためにnilを入れておいて印にしておく decorators.push(nil) end end # デコレータ情報の一覧を取得。デコレータはメソッド名をキーにしたHashで返す def self.get_decorators(klass) result = Hash.new{|hash, key| hash[key] = []} @@__decorators[klass].each { |decorator| result[decorator.method_name].push(decorator) if decorator != nil } result end end # デコレータ1個の情報を持つクラス。宣言された数だけ生成される。 class Decorator attr_reader :decorator, :args, :method_name def initialize(decorator, args) @decorator = decorator @args = args @method_name = nil end def regist_method(method) @method_name = method end end # デコレータを有効にします module DecoratorEnabler def self.included(base) base.extend(DecoratorAPI) end # この中で、メソッドの置き換え、外部への登録などを行う # 今はただ画面にデコレータの属性を表示しているだけ def initialize p Decorators::get_decorators(self.class) end end # 実際にユーザが使えるデコレータの中身が定義されている module DecoratorAPI def method_added(method) Decorators::regist_method_to_last_decorator(self, method) end # Turbo Gearsライクな、exposeデコレータ(サンプル) def expose Decorators::new_decorator(self, :expose, []) end end
ちょっとデコレータっぽいことをやるだけでも相当長くなってしまいました。実際に、「自分のプログラムでも取り入れたい」と思われた方が、実際にやるためには、以下のポイントをカスタマイズする必要があります。
- DecoratorAPIを作って、デコレータの登録部分を必要なだけ追加する
- DecoratorEnablerのinitializeを書き換え、実際に取得した情報を元に、外部にメソッドの情報を登録するなり、定義されているメソッドを置き換えて別の実装にするなり、色々楽しいことをする
さ、とりあえず、デコレータっぽいことをやるやり方が分かったので、実際にこれを使ったライブラリでも作ってみようかな?もうちょっとがんばって、
@@expose def exposed_method return "hello end
とかできれば、もうちょっと見た目がデコレータっぽくできるかも。いやぁ、Rubyって難しいわ。