14

我被困住了。我正在尝试动态定义一个类方法,但我无法理解 ruby​​ 元类模型。考虑以下类:

class Example

  def self.meta; (class << self; self; end); end

  def self.class_instance; self; end

end

Example.class_instance.class # => Class
Example.meta.class           # => Class

Example.class_instance  == Example      # => true
Example.class_instance  == Example.meta # => false

显然这两种方法都返回一个 Class 的实例。但这两个实例并不相同。他们也有不同的祖先:

Example.meta.ancestors            # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors  # => [Example, Object, Kernel]

区分元类和类实例有什么意义?

我发现,我可以send :define_method对元类动态定义一个方法,但是如果我尝试将它发送到类实例,它将无法工作。至少我可以解决我的问题,但我仍然想了解它为什么会这样工作。

更新 2010 年 3 月 15 日 13:40

以下假设是否正确。

  • 如果我有一个调用 self.instance_eval 并定义一个方法的实例方法,它只会影响该类的特定实例。
  • 如果我有一个调用 self.class.instance_eval 的实例方法(这与调用 class_eval 相同)并定义了一个方法,它将影响该特定类的所有实例,从而产生一个新的实例方法。
  • 如果我有一个调用 instance_eval 并定义一个方法的类方法,它将为所有实例生成一个新的实例方法。
  • 如果我有一个类方法,它在元/特征类上调用 instance_eval 并定义一个方法,它将产生一个类方法。

我认为它开始对我有意义。如果类方法中的 self 指向 eigen 类,那肯定会限制您的可能性。如果是这样,就不可能从类方法内部定义实例方法。那是对的吗?

4

2 回答 2

11

使用时动态定义单例方法很简单instance_eval

Example.instance_eval{ def square(n); n*n; end }
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27

至于上面的区别,你混淆了两件事:

您定义的元类,在 Ruby 社区中称为singelton 类eigen 类。该单例类是您可以添加类(单例)方法的类。

至于您尝试使用该class_instance方法定义的类实例,只不过是类本身,为了证明这一点,只需尝试向该类添加一个实例方法Example并通过检查存在来检查class_instance您定义的方法是否返回该类Example本身该方法的:

class Example
  def self.meta; (class << self; self; end); end
  def self.class_instance; self; end
  def hey; puts hey; end
end

Example.class_instance.instance_methods(false) #=> ['hey']

无论如何,为您总结一下,当您想添加类方法时,只需将它们添加到该元类即可。至于class_instance方法没用,去掉就好了。

无论如何,我建议您阅读这篇文章以掌握 Ruby 反射系统的一些概念。

更新

我建议您阅读这篇不错的文章:Fun with Ruby's instance_eval 和 class_eval,不幸的是class_eval并且instance_eval令人困惑,因为它们以某种方式违背了它们的命名!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

现在回答你的假设:

如果我有一个调用 self.instance_eval 并定义一个方法的实例方法,它只会影响该类的特定实例。

是的:

class Foo
  def assumption1()
    self.instance_eval("def test_assumption_1; puts 'works'; end")
  end
end

f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []

如果我有一个调用 self.class.instance_eval 的实例方法(这与调用 class_eval 相同)并定义了一个方法,它将影响该特定类的所有实例,从而产生一个新的实例方法。

noinstance_eval在该上下文中将在类本身上定义单例方法(不是实例方法):

class Foo
  def assumption2()
    self.class.instance_eval("def test_assumption_2; puts 'works'; end")
  end
end

f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]

为此,请替换instance_evalclass_eval上述内容。

如果我有一个调用 instance_eval 并定义一个方法的类方法,它将为所有实例生成一个新的实例方法。

没有:

class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

Foo.singleton_methods(false) #=> ["assumption_3"]

这将使单例方法,而不是实例方法。为此,请替换instance_evalclass_eval上述内容。

如果我有一个类方法,它在元/特征类上调用 instance_eval 并定义一个方法,它将产生一个类方法。

好吧,不,这会产生如此复杂的东西,因为它会将单例方法添加到单例类中,我认为这不会有任何实际用途。

于 2010-03-15T11:12:04.453 回答
5

如果你在一个上定义了一个方法,它就可以在它的对象上被调用。它是一个实例方法

class Example
end

Example.send :define_method, :foo do
  puts "foo"
end

Example.new.foo
#=> "foo"

如果您在元类上定义方法,则可以在该类上调用它。这类似于其他语言中的类方法或静态方法的概念。

class Example
  def self.metaclass
    class << self
      self
    end
  end
end

Example.metaclass.send :define_method, :bar do
  puts "bar"
end

Example.bar
#=> "bar"

元类存在的原因是你可以在 Ruby 中做到这一点:

str = "hello"
class << str
  def output
    puts self
  end
end

str.output
#=> "hello"

"hi".output
# NoMethodError

如您所见,我们定义了一个仅对String 的一个实例可用的方法。我们定义这个方法的东西叫做元类。在方法查找链中,首先访问元类,然后再搜索对象的类。

如果我们用 typeString的对象替换 type 的对象Class,你可以想象为什么这意味着我们只在特定的类上定义一个方法,而不是在所有的类上。

当前上下文和当前上下文之间的差异很微妙,如果您有兴趣self,可以阅读更多内容。

于 2010-03-15T12:13:38.243 回答