我试图在ruby中简单地实现AOP。我能够在建议之前和之后实施,我不知所措。
这是将被建议的目标类:
class MyClass
def method
puts "running method"
end
end
这是Aspect类,用于实例化能够提供建议的对象:
class Aspect
def advise(class_name, method, type, &block)
class_name.send(:alias_method, :proceed, :method)
class_name.send(:define_method, :method) do
case type
when :before
yield
proceed
when :after
proceed
yield
when :around
yield(proceed) # * proceed is the old version of the method
end
end
end
end
(*)当调用方法时,Yield应执行MyClass周围的块#继续当前对象。
创建目标和方面:
mc = MyClass.new
a = Aspect.new()
在没有建议的情况下调用该方法:
puts mc.method
向周围提供MyClass#方法:
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
puts mc.method
(*)我无法传递一些东西来识别继续调用,即在没有建议的情况下调用旧方法。
输出应为:
First
running method
Last
答案 0 :(得分:1)
在Ruby中,方法调用如下所示:
receiver.method(arguments)
或者,如果接收者是self
,您可以离开接收器。
因此,要在某个接收器上调用名为proceed
的方法,您需要编写
receiver.proceed
但是,在您的实施过程中,您无法跟踪接收器应该是什么,因此,由于您不了解接收器,因此您无法调用该方法。
请注意,您的方法还存在许多其他问题。例如,如果您建议使用多个方法,则将它们全部替换为相同的方法,相互覆盖。
答案 1 :(得分:0)
我相信这里有两件事情出错。
此部分代码
when :around
yield(proceed) # * proceed is the old version of the method
end
调用给定的块以建议将proceed方法的输出作为参数提供。 所以你的输出可能看起来像:
running method
First
Last
此块
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
只是评估作为进行给出的参数。如果给出方法,则不会调用它。因此,在你的情况下考虑问题1,方法的原始定义(别名为proceed)返回nil(return的输出),当产生时,它将作为值传递给块中的proceed参数。该块最终评估为
puts "First"
nil
puts "Last"
mc.method被调用。
要解决第二部分,您可能需要考虑使用send。因为调用它的代码可能不知道您方面的内部工作方式。它可能会随着时间的推移而发生变化,因此所谓的Aspect.advise不应该假设原始方法仍然可以访问。相反,它应该采用一个参数(新方法名称)并将其发送到对象。将块传递给建议:
a.advise(MyClass, :method, :around) do |aliased_method_name|
puts "First"
send(aliased_method_name)
puts "Last"
end
在调用建议时调整添加到班级的周围项目:
when :around
yield(:proceed) # * proceed is the old version of the method
end
如果你同时做这两件事,你的周围部分会调用提供的块,使用重写方法的新别名的符号。
N.B。:这种方法不适用于需要任何参数的方法。
答案 2 :(得分:0)
这就是我所做的。在Aspect#advise的定义中,我现在使用Proc,就像这样:
when :around
yield Proc.new { proceed }
end
当调用方法来建议MyClass#方法:around参数我使用:
a.advise(MyClass, :method, :around) do |original|
puts "First"
original.call
puts "Last"
end
我得到了:
First
running method
Last
答案 3 :(得分:0)
以下是适用于参数的固定版本,并避免破坏。
class Aspect
@@count = 0
def self.advise(class_name, method, type=nil, &block)
old_method = :"__aspect_#{method}_#{@@count += 1}"
class_name.send(:alias_method, old_method, method)
class_name.send(:define_method, method) do |*args, &callblock|
case type
when :before
yield
send(old_method, *args, &callblock)
when :after
send(old_method, *args, &callblock)
yield
when :around, nil
yield lambda {
send(old_method, *args, &callblock)
}
end
end
end
end
class Foo
def foo(what)
puts "Hello, #{what}!"
end
end
Aspect.advise(Foo, :foo) do |y|
puts "before around"
y.yield
puts "after around"
end
Aspect.advise(Foo, :foo, :before) do
puts "before"
end
Aspect.advise(Foo, :foo, :after) do
puts "after"
end
Foo.new.foo("world")
# before
# before around
# Hello, world!
# after around
# after