5

我有以下来自 Programming Ruby 1.9 的代码(略微改编)我只是想确保我的思考过程是准确的

module Trace
  def self.included(culprit)
    #Inject existing methods with tracing code:
    culprit.instance_methods(false).each do |func|
      inject(culprit, func)
    end

    #Override the singletons method_added to ensure all future methods are injected.
    def culprit.method_added(meth)
      unless @trace_calls_internal
        @trace_calls_internal = true
        Trace.inject(self, meth) #This will call method_added itself, the condition prevents infinite recursion.
        @trace_calls_internal = false
      end
    end
  end

  def self.inject(target, meth)
    target.instance_eval do
      #Get the method
      method_object = instance_method(meth)
      #Rewrite dat sheet
      define_method(meth) do |*args, &block|
        puts "==> Called #{meth} with #{args.inspect}"
        #the bind to self will put the context back to the class.
        result = method_object.bind(self).call(*args, &block)
        puts "<== #{meth} returned #{result.inspect}"
        result
      end
    end
  end
end

class Example
  def one(arg)
    puts "One called with #{arg}"
  end
end
#No tracing for the above.
ex1 = Example.new
ex1.one("Sup") #Not affected by Trace::inject

class Example #extend the class to include tracing.
  include Trace #calls Trace::inject on existing methods via Trace::included
  def two(a1, a2) #triggers Example::method_added(two) and by extension Trace::inject
    a1 + a2
  end
end

ex1.one("Sup again") #Affected by Trace::inject
puts ex1.two(5, 4) #Affected by Trace::inject

我仍在努力思考这是如何工作的。我希望是否有人可以确认我的思维过程,因为我想确保我了解这里发生了什么。评论是我自己添加的。我真的认为我对方法绑定、单例类和元编程的理解充其量只是新手。

首先,任何包含它的类都会调用 Trace::included。这个方法做了两件事,获取该类中现有函数的列表(如果有的话)并使用注入覆盖它们的方法。然后它修改包含该模块的类的单例类,并覆盖默认的 method_added 方法,以确保每次添加一个方法超过附加的包含注入都会影响它。此方法使用变量来防止无限递归,因为对注入的调用将根据其性质调用 method_add。

Trace:: 工作如下:使用 instance_eval 将 self 设置为存在于类定义中的上下文。因此范围(?)被修改为它在该类定义中的样子。

然后我们将 method_object 设置为 instance_method(meth) ,这将获得要添加的原始方法。由于instance_method 没有明确的接收者,它会默认为 self ,这与类定义中的上下文相同?

然后我们使用define_method来定义一个同名的方法。因为我们是在instance_eval的上下文中,这相当于定义了一个实例方法,会覆盖现有的方法。我们的方法接受任意数量的参数和一个块(如果存在)。

我们添加一些耀斑来放置我们的“跟踪”,然后我们还调用我们存储在 method_object 中的原始方法,因为原始方法正在被覆盖。这个方法是未绑定的,所以我们必须使用 bind(self) 将它绑定到当前上下文,以便它具有与最初相同的上下文?然后我们使用 call 并传递参数和块,存储它的返回值,并在打印后返回它的返回值。


我真的希望我能充分描述这一点。我的描述准确吗?我对粗体内容和以下行特别不确定:

method_object.bind(self).call(*args, &block)
4

2 回答 2

2

你的描述没问题!

基本上,Trace 模块所做的是包装类方法,因此您可以在方法执行之前/之后运行任何代码;对调用者透明。

Trace 模块使用 Ruby Hooks,一些在发生某些事情时调用的方法(模块包含、添加的方法等)。你可以在网上找到一些关于它的信息,例如:

在这段代码中:

method_object.bind(self).call(*args, &block)

正如您所提到的,您正在使用原始方法上下文调用未绑定的方法(自我引用原始对象,因为我们在 instance_eval 方法内部)并“重新传递”任何参数或块给它。

需要注意的是,method_object 是一个未绑定的方法,所以你必须给它绑定一个上下文,否则会抛出 NoMethodError 异常。

于 2013-06-05T20:03:11.563 回答
2

Trace:: 工作如下:使用 instance_eval 将 self 设置为存在于类定义中的上下文。因此范围(?)被修改为它在该类定义中的样子。

使用实例 eval 您可以评估将 self 绑定到对象的块,在这种情况下,它将是包含模块的类。(即罪魁祸首)。只是为了清楚起见,有以下区别:

o = Object.new
o.instance_eval do
  puts self
end

class Foo < Object end
Foo.instance_eval do  puts self end

回答:所以是的,您在这个假设中是正确的!


然后我们将 method_object 设置为 instance_method(meth) ,这将获得要添加的原始方法。由于 instance_method 没有明确的接收者,它会默认为 self ,这与类定义中的上下文相同?

是的,你的假设是正确的。请注意,询问:

culprit.instance_methods(false) => [:someselector, :someotherselector]

而在这个上下文中调用实例方法确实和调用self.instance_method是一样的。


这个方法是未绑定的,所以我们必须使用 bind(self) 将它绑定到当前上下文,以便它具有与最初相同的上下文?

是的。当您以跟踪模块中定义的方式获取方法时,您将获得一个未绑定的方法对象,该对象可以按照描述再次绑定。


如果你想深入研究 Ruby 的元编程,我推荐以下书籍: http: //pragprog.com/book/ppmetr/metaprogramming-ruby它解释了 Ruby 的对象系统、mixin、块和任何你能想象到的所有细节.

于 2013-06-08T09:50:21.763 回答