这种动态方法定义的工作原理和原因是什么?

时间:2012-07-08 16:14:15

标签: ruby metaprogramming

以下代码如何运作,更重要的是,为什么它会以这种方式运作?

class Example
  def one
    def one
      @value = 99
    end
    puts "Expensive Call"
    @value = 99 # assume its expensive call
  end
end

ex = Example.new
puts ex.one # => "Expensive Call"; 99
puts ex.one # => 99

这里,在第一次调用方法one时,Ruby执行外部one方法,但在连续调用时,它只执行内部one方法,绕过外部{{1}方法完全。

我想知道它是如何发生的,为什么会这样。

3 个答案:

答案 0 :(得分:7)

第一次执行时,它会在类中重新定义,然后完成。第二次,方法one本身已被覆盖为@value = 99,因此不会打印任何内容。

答案 1 :(得分:6)

如何运作

Ruby允许您在运行时重新定义类,因为 class def 实际上是可执行代码。在您的示例中,代码执行以下操作:

  1. 定义一个Example#one方法,它将在调用实例方法时(重新)定义Example#one方法。
  2. 出于实际目的,在调用外部实例方法之前,不会执行内部 def 。 (头发拆分器可能合法地争论这个定义,但是这会解释为解析器/解释器的细节,这对于本讨论而言并不重要。)
  3. 您定义名为“ex。”的示例实例
  4. 您在 ex 上调用实例方法,该方法定义了具有相同名称的 new 方法。
  5. 再次调用实例方法时,将使用新方法而不是旧方法。
  6. 为什么会起作用

    基本上,该命名空间中方法replaces any earlier definitions的最后一个定义,但这些方法实际上是新对象。您可以按如下方式查看此操作:

    def my_method
      puts 'Old Method'
      puts  self.method(:my_method).object_id
      def my_method
        puts 'New Method'
        puts  self.method(:my_method).object_id
      end  
    end
    

    如果你在irb或pry会话中运行它,你可以看到在运行时重新定义的方法:

    > my_method; puts; my_method
    Old Method
    8998420
    
    New Method
    8998360
    

    正如您可以通过不同的对象ID看到的那样,即使方法具有相同的名称并且附加到同一对象(通常是控制台上的 main ),它们实际上也是不同的方法对象。但是,由于方法是使用相同的名称定义的,因此当实例执行method lookup时,只会找到最新的定义。

答案 2 :(得分:2)

首先要认识到Ruby 中没有内部或外部方法这样的东西。

您正在方法中定义一个新方法 - 在这种情况下,由于定义的方法与现有方法具有相同的名称,因此新定义将完全覆盖原始方法。

你拥有的东西相当于(或许)更明显:

class Example
  def one
    self.class.send(:define_method, :one) do
      @value = 99
    end
    puts "Expensive Call"
    @value = 99 # assume its expensive call
  end
end

这里更清楚的是你在类的上下文中定义了一个方法。