在ruby中实现天真的方面

时间:2014-11-27 08:49:01

标签: ruby metaprogramming aop

我试图在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

4 个答案:

答案 0 :(得分:1)

在Ruby中,方法调用如下所示:

receiver.method(arguments)

或者,如果接收者是self,您可以离开接收器。

因此,要在某个接收器上调用名为proceed的方法,您需要编写

receiver.proceed

但是,在您的实施过程中,您无法跟踪接收器应该是什么,因此,由于您不了解接收器,因此您无法调用该方法。

请注意,您的方法还存在许多其他问题。例如,如果您建议使用多个方法,则将它们全部替换为相同的方法,相互覆盖。

答案 1 :(得分:0)

我相信这里有两件事情出错。

  1. 此部分代码

    when :around
      yield(proceed) # * proceed is the old version of the method
    end
    
  2. 调用给定的块以建议将proceed方法的输出作为参数提供。 所以你的输出可能看起来像:

    running method
    First
    Last
    
    1. 此块

      a.advise(MyClass, :method, :around) do |proceed|
        puts "First"
        proceed # this is not working *
        puts "Last"
      end
      
    2. 只是评估作为进行给出的参数。如果给出方法,则不会调用它。因此,在你的情况下考虑问题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