| 著作一覧 |
RjbがimportしたJavaのクラスをRjbはObjectとして持っている。
require 'rjb'
jstring = Rjb::import('java.lang.String')
p jstring # => #<Rjb::Java_lang_String:0x2f0490>
だから、newメソッドは他のクラスと異なりインスタンスメソッドとなっている。
p jstring.method(:new) #=> #<Method: #<Rjb::Java_lang_String:0x2d0334>.new> p Array.method(:new) #=> #<Method: Class#new>
そのため、newメソッドに介入して作られたJavaオブジェクトのプロクシにメソッドを追加したりできる。
module RjbAddon
def java_class
self._classname
end
end
jstring.instance_eval do
alias :org_new :new
def new(*args)
pxy = org_new(*args)
pxy.extend RjbAddon
pxy
end
end
hello = jstring.new('hello')
p hello.to_string # => "hello" /* Java method */
p hello.java_class # => "java.lang.String"
ここで、RjbのJavaクラスのnewメソッドに介在してメソッドを追加する汎用の仕組みを考える。
module RjbAddon
def java_class
self._classname
end
def self.mixin(jclass, &proc)
jclass.instance_eval do
alias :org_new :new
def new(*args)
pxy = org_new(*args)
pxy.extend RjbAddon
pxy
end
end
end
end
RjbAddon.mixin(jstring)
RjbAddon.mixinに追加のブロックを与えて、そこで定義したメソッドを組み込ませたい。
RjbAddon.mixin(jstring) do # こう書けるようにしたい。
def say_hello
puts 'hello'
end
end
hello = jstring.new('hello')
hello.say_hello # => 'hello'
しかし、次のようにmixinメソッドを定義してもうまくいかない。
module RjbAddon
...
def self.mixin(jclass, &proc)
jclass.instance_eval do
alias :org_new :new
def new(*args)
pxy = org_new(*args)
pxy.extend RjbAddon
pxy.instance_eval do
proc.call # => tried to create Proc object without a block (ArgumentError)
end
pxy
end
end
end
end
instance_evalの中でスコープが変わる(通常のブロックとは違うわけだ)ので、procという仮引数が見えなくなるからだ。
そこで、RjbのJavaオブジェクトのインスタンス変数としてみる。
def self.mixin(jclass, &proc)
jclass.instance_eval do
@user_new = proc
alias :org_new :new
def new(*args)
p @user_new # => # 見える
pxy = org_new(*args)
pxy.extend RjbAddon
pxy.instance_eval do
p @user_new # => nil ここのselfはpxyなので見えない
@user_new.call
end
pxy
end
end
end
そこで外ざしできるようにする。
def user_initialize(proc) # 外ざし用メソッド(ミキシンする)
instance_eval do
proc.call
end
end
def self.mixin(jclass, &proc)
jclass.instance_eval do
@user_new = proc
alias :org_new :new
def new(*args)
p @user_new
pxy = org_new(*args)
pxy.extend RjbAddon
pxy.user_initialize @user_new # 外ざし
pxy
end
end
end
しかし、これもうまくいかない。
p hello.say_hello # => in `method_missing': Fail: unknown method name `say_hello' (RuntimeError)
与えたprocのselfがmainのままだからだ。
しょうがないので、ミキシンメソッド呼び出し時に与えるブロックでselfを取れるようにしてみる。
module RjbAddon
def java_class
self._classname
end
def user_initialize(proc)
instance_eval do
proc.call self # instance_eval内のself
end
end
def self.mixin(jclass, &proc)
jclass.instance_eval do
@user_new = proc
alias :org_new :new
def new(*args)
pxy = org_new(*args)
pxy.extend RjbAddon
pxy.user_initialize @user_new
pxy
end
end
end
end
RjbAddon.mixin(jstring) do |x| # xはプロクシ自身
def x.say_hello # 自身の特異メソッドなので利用可能
puts 'hello'
end
end
...
hello.say_hello # => hello
しかし、mixinメソッドにブロックを与えて、そこにプロクシのselfが渡されるのは気に食わない。(def x.say_hello と書くところ)
どうすればmixinメソッドに与えるブロックのselfをそのまま作成されたプロクシのselfとできるだろうか?
ジェズイットを見習え |
org_new = method(:new)<br>define_method(:new) do |*args|<br> pxy = org_new.call(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval(&proc)<br> pxy<br>end
ああ、一番の問題は、instance_evalでブロックを作っていることなのか。&proc として与えれば良いということに気づいてませんでした。<br>define_methodはこの場合あまり使いやすくなかったので、こんな感じ。<br> def self.mixin(jclass, &proc)<br> jclass.instance_eval do<br> @user_new = proc<br> alias :org_new :new<br> def new(*args)<br> pxy = org_new(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval &@user_new<br> pxy<br> end<br> end<br> end<br>でも、define_methodを使うとインスタンス変数が不要になるのは良いかも。<br> def self.mixin(jclass, &proc)<br> define_method(:user_init) do<br> instance_eval(&proc)<br> end<br> jclass.instance_eval do<br> alias :org_new :new<br> def new(*args)<br> pxy = org_new(*args)<br> pxy.extend RjbAddon<br> pxy.user_init<br> pxy<br> end<br> end<br> end
あ、最後に書いたやつはだめでした。user_initはあくまでもjclassのインスタンスメソッドにしなければならないから(RjbAddonモジュールのメソッドではない)<br>。
同じjclassについて二回呼んだ場合<br>* aliasを使うとorig_newが無限再帰になる<br>* インスタンス変数は上書きされる<br>に注意
orig_newはsuperでいいんじゃないかな
あ、二回呼んだら前のが無視されちゃうか
>orig_newはsuperでいいんじゃないかな<br>Class.newじゃないからだと思うけどsupreに変えたら、unknown method name 'new' (RuntimError)になりました。
>* aliasを使うとorig_newが無限再帰になる <br>確かに(試した: `org_new': stack level too deep (SystemStackError)。取り出したら保存が必要ですね。<br> def self.mixin(jclass, &proc)<br> jclass.instance_eval do<br> @user_new = proc<br> alias :org_new :new<br> @org_new ||= method(:org_new)<br> def new(*args)<br> pxy = @org_new.call(*args)<br> pxy.extend RjbAddon<br> pxy.instance_eval &@user_new<br> pxy<br> end<br> end<br> end
Methodで保存するならalias不要
そりゃそうか。