define_methodを使ったインターセプタ
前に作ったmethod_missingを使ったインターセプタですが、これには「インターセプタ適用対象のオブジェクト内で呼び出した自身のメソッドにインターセプタが適用されない」という問題があります。まぁ、わかっていたことですが、それだと困る状況に陥ったため、define_methodを使ってインターセプタを織り込む奴を作ってみました。
- インターセプタ適用対象のメソッドに上書きして、インターセプタ織り込み済みメソッドを定義します。
- define_methodを利用
- これにより、インターセプタ適用対象のオブジェクト内から自身のメソッドを呼び出した場合にもインターセプタが適用されるようになります。
- もともとのメソッドは別名をつけて保存しておきます。
- MethodInvocationのproceed()ではこいつを実行します。
オブジェクトの特異メソッドを任意の名前で定義する方法がわからなかったので、Class自体を拡張するようにしました。とりあえずは問題ないしいいかな。
ということで実装です。
module Aspect class MethodInvocation def initialize( this, name, arguments, old ) @arguments = arguments @this = this @name = name @old = old end def proceed( ) @this.send( @old.to_sym, *@arguments ) end attr :arguments, true attr :name attr :this end module_function # #== クラスにインターセプタを適用する。 # def apply( clazz, name, interceptor ) old = name.to_s + "_without_" + interceptor[:name] # もともとのメソッドがあれば、別名でキープしておく clazz.__send__(:alias_method, old.to_sym, name ) if clazz.method_defined? name # インターセプタ適用対象のメソッドを上書きで定義。 clazz.__send__(:define_method, name ) {|*args| mi = MethodInvocation.new( self, name, args, old.to_sym ) interceptor[:block].call( mi ) } end # #== インターセプタを作る。 # def interceptor( name, &block ) # とりあえずハッシュでいいかな。 return { :name=>name, :block=>block } end end
使い方例。
# テスト用クラス class Test def foo( str ); puts "foo - #{str} "; end def var( str ); puts "var - #{str} "; end def hoge( str ) foo("hoge"+str) # 自身のメソッドを呼び出す。 end end # インターセプタを適用。 Aspect.apply( Test, :foo, Aspect.interceptor("edit") {|mi| # メソッド呼び出しの前後でメソッド名を出力し、 # 第一引数を"x"に変換するインターセプタ puts "start " + mi.name.to_s mi.arguments[0] = "x" mi.proceed # もともとのメソッドを実行。 puts "end " + mi.name.to_s } ) # 呼び出してみる。 test = Test.new test.foo("a") puts "---" test.var("a") puts "---" test.hoge("a") puts "---" test.foo_without_edit("a") # もともとのメソッドが別名で定義されている。
実行結果です。
start foo foo - x end foo --- var - a --- start foo foo - x end foo --- foo - a
ちゃんとテストできてないけど、こんな感じでいけそうかな。