我有来自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由包含它的任何类调用。此方法执行两项操作,获取该类中现有函数的列表(如果有)并使用inject重写其方法。然后它修改包含模块的类的单例类,并覆盖默认的method_added方法,以确保每次添加方法时,附加的include注入都会影响它。这个方法使用一个变量来防止无限递归,因为对inject的调用会引起method_added的性质。
Trace ::的工作方式如下:使用instance_eval将self设置为类定义中存在的上下文。因此,范围(?)会被修改为该类定义中的范围。
然后我们将method_object设置为instance_method(meth),它将获得将添加的原始方法。由于 instance_method没有显式接收器,它将默认为self,这与在类定义中的上下文相同?
然后我们使用define_method来定义具有相同名称的方法。因为我们位于instance_eval的上下文中,所以这相当于定义实例方法并将覆盖现有方法。我们的方法接受任意数量的参数和一个块(如果存在)。
我们添加了一些flare来放置我们的“Tracing”,然后我们也调用我们存储在method_object中的原始方法,因为原始文件被覆盖了。这个方法是 unbound,所以我们必须使用bind(self)将它绑定到当前上下文,因此它具有与原来相同的上下文?然后我们使用call并传递参数和块,存储其返回值,并在打印后返回其返回值。
我真的希望我能够充分地描述这一点。我的描述准确吗?我特别不确定粗体内容和以下内容:
method_object.bind(self).call(*args, &block)
答案 0 :(得分:2)
您的描述还可以!
基本上,Trace模块所做的是包装类方法,因此您可以在方法执行之前/之后运行任何代码;透明地为来电者。
Trace模块使用Ruby Hooks,一些在发生事件时调用的方法(模块包含,添加方法等)。你可以在网上找到一些关于它的信息,例如:
在此代码中:
method_object.bind(self).call(*args, &block)
正如您所提到的,您使用原始方法上下文调用未绑定方法(对原始对象的自引用引用,因为我们在instance_eval方法中)并且“修复”任何参数或块。
重要的是要注意,method_object是一个未绑定的方法,因此必须必须绑定一个上下文,否则会抛出NoMethodError异常。
答案 1 :(得分:2)
Trace ::的工作原理如下:使用instance_eval将self设置为类定义中存在的上下文。因此范围(?)是 修改为在该类定义中的方式。
使用实例eval,您可以自我绑定对象来评估块,在这种情况下,它将是包含模块的类。 (即罪魁祸首)。为了清楚起见,之间存在差异:
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没有 有一个明确的接收器,它将默认为自己将是 与在类定义中的上下文相同?
是的,你的假设是正确的。请注意,问:
culprit.instance_methods(false) => [:someselector, :someotherselector]
在此上下文中调用实例方法确实与调用self.instance_method相同。
此方法未绑定,因此我们必须将其绑定到当前上下文 使用bind(self)所以它具有与原来相同的上下文?
是。当您以跟踪模块中定义的方式获取方法时,您将获得一个未绑定的方法对象,该对象可以按照描述再次绑定。
如果你想深入研究Ruby的元编程,我推荐下面的书:http://pragprog.com/book/ppmetr/metaprogramming-ruby它解释了Ruby的对象系统,mixins,块和你能想象的任何细节背后的所有细节。