11

我试图理解 Ruby 对象模型。我知道实例方法保存在类中而不是类的对象中,因为它消除了冗余。我读到,每当创建一个类时,也会为新创建的类创建一个元类。元类存储类方法。即类的单例方法位于元类中。例如

class MyClass
  def hi
    'hi object'
  end

  def self.bye
    'bye singleton method'
  end
end

对于上面的 MyClass,也创建了一个元类(比如#MyClass)。现在方法“hi”是一个实例级方法,可以在 MyClass 的所有对象上调用。方法“再见”是 MyClass 的单例方法,它位于 #MyClass 中。'hi' 保存在 MyClass 而不是 MyClass 的所有对象中的原因(我认为是这样)是因为它避免了冗余。但是我们不能有多个名为 MyClass 的类。那么为什么不将“再见”存储在 MyClass 而不是 #MyClass 中,因为我们不能拥有多个 MyClass。我完全不知道为什么会这样,我只是想了解其背后的原因。

- - -更新 - -

元类存储类信息,如单例方法和其他东西。但是既然一个类是一个单例对象(它是类 Class 的一个实例并且是它的类型)那么为什么不将所有信息保存在类本身而不是元​​类中。

4

4 回答 4

11

只是为了超级清楚。

这是一个解释问题的快速 ruby​​ 脚本:

#!/usr/bin/env ruby
puts ObjectSpace.count_objects[:T_CLASS] #>> 471
class X
  def self.foo
  end
  def bar
  end
end
puts ObjectSpace.count_objects[:T_CLASS] #>> 473

这就是“ObjectSpace.count_objects[:T_CLASS] 将计数增加 2”的 OP 含义。让我们将额外的类称为 X 的单例类,因为这似乎是 Ruby 内部对其的称呼。

irb> X
=> X
irb> X.singleton_class
=> <Class: X>

请注意,该#foo方法是 的实例方法X.singleton_class,而不是X

irb> X.instance_methods(false)
=> [:baz]
irb> X.singleton_class.instance_methods(false)
=> [:foo]

那么为什么:foo存储在X.singleton_class而不是X?不是只有一个X吗?

我认为主要原因是一致性。考虑以下关于普通实例对象的更简单的场景。

car = Car.new
def car.go_forth_and_conquer
end

正如@mikej 出色地解释的那样,这个新方法存储在汽车的单例类中。

irb> car.singleton_class.instance_methods(false)
=> [:go_forth_and_conquer]

类是对象

现在,类也是对象。每个类都是一个实例Class。因此,当X定义了一个类(比如Classcar

Car = Class.new do
  def go_forth_and_conquer
    puts "vroom"
  end
end
Car.new.go_forth_and_conquer

因此,仅重用代码并以相同的方式执行(即保留fooX.singleton_class.Class实例。

可能无关紧要

您可能会想,如果 Ruby 没有用于 实例的单例类Class,那么可能会节省一些内存。但是,在我看来,bar实际存储的位置是我们可能不应该指望的实现细节。只要行为一致,Rubinius、MRI 和 JRuby 都可以不同地存储方法和实例。据我们所知,只要整体行为符合 ruby​​ 规范,出于与您概述的完全相同的原因,可能有一个合理的 Ruby 实现不会急切地为类对象创建单例类。#singleton_class(例如,在第一次调用该方法之前,实际的单例类不存在。)

于 2013-07-17T23:35:11.557 回答
3

这不是您问题的完全答案,但它可能很有用。考虑两件事可能会有所帮助:

  • 当您想到元前缀在其他场景中的使用方式时,元类并不是一个很好的名称。您将在其他文档中看到使用的eigenclass可能是一个更好的名称,意思是“对象自己的类”
  • 不仅仅是类有特征类,每个对象都有

eigenclass 用于存储特定于特定对象的方法。例如,我们可以为单个 String 对象添加一个方法:

my_string = 'Example'
def my_string.example_method
  puts "Just an example"
end

此方法只能在任何其他 String 对象上调用,my_string而不能在任何其他 String 对象上调用。我们可以看到它存储在my_string的 eigenclass 中:

eigenclass = class << my_string; self; end # get hold of my_string's eigenclass
eigenclass.instance_methods(false) # => [:example_method]

记住类是对象,在这种情况下,特定于特定类的方法应该存储在该类的特征类中是有意义的。


更新:实际上,特征类有一个特征类。如果我们将eigenclass其作为方法添加到 Object,我们可以更容易地看到这一点:

class Object 
  def eigenclass 
    class << self
      self
    end 
  end 
end

然后我们可以这样做:

irb(main):049:0> my_string.eigenclass
=> #<Class:#<String:0x269ec98>>
irb(main):050:0> my_string.eigenclass.eigenclass
=> #<Class:#<Class:#<String:0x269ec98>>>
irb(main):051:0> my_string.eigenclass.eigenclass.eigenclass # etc!
=> #<Class:#<Class:#<Class:#<String:0x269ec98>>>>

虽然这似乎造成了无限的倒退,但这是可以避免的,因为 Ruby 只在需要时创建特征类。我认为“元类”这个名称确实是您感到困惑的部分原因,因为您期望“元类”包含某种实际上没有的信息。

于 2013-07-09T15:55:25.940 回答
2

原因真的归结为是什么self。对于任何给定的方法,self都是该方法定义的对象的一个​​实例。

在实例方法中,存储在 上MyClassself将是MyClass您从中调用的实例#hi。在类方法中,self将是元类的实例(即类本身)。对元类进行操作意味着 的概念self没有改变,即使在对本身只是其元类的单例实例的类进行操作时也是如此。

于 2013-07-09T15:52:32.890 回答
2

根据Ruby 编程语言 ,类方法实际上是类实例上的单例方法,与类同名。

Class Foo
  def self.bar
    "Am a class method"
  end
end

这里的方法self.bar可以描述为类型实例上的单例方法FooClass Foo

#the following code is just to explain on what actually are class methods called
Foo.bar #=> "Am a class method" 
#the call is done on an instance of class Foo which got ref name Foo

我们可以继续添加更多的类/单例/元类Foo方法

class<<Foo
  def self.another_bar
    "Am another class method"
  end
end

Foo.another_bar #=>"Am another class method"

更正式的单例方法被定义为匿名特征类/元类的实例方法。

尽管在概念上是错误的,但我们可以假设类在这种情况下是对象,以便更好地掌握。

这个概念可以在语言的所有级别中引入真正的面向对象。Objective-C 以类似的方式实现类方法。

在 Obj-C 中,元类作为包含有关它的类的信息的类而退出meta。继承的原则也适用于元类,超类是它的类的超类的元类,并且向上爬,所以直到它到达基础对象,谁的元类就是元类本身。更多关于这方面的阅读可以在这里完成。

于 2013-07-09T16:00:07.897 回答