読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

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 

ちゃんとテストできてないけど、こんな感じでいけそうかな。