为什么在ruby中创建元类?

时间:2013-07-09 15:22:44

标签: ruby

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

class MyClass
  def hi
    'hi object'
  end

  def self.bye
    'bye singleton method'
  end
end

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

----- ---- UPDATE

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

4 个答案:

答案 0 :(得分:6)

只是要超级清楚。

这是一个快速的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]的OP意味着将计数增加2”。让我们将额外的类称为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)时,ruby实际上是创建Class的实例,然后向实例添加方法(类似于我们对car所做的操作。)例如,这是创建新类的另一种方法

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

因此,重用代码并以相同的方式执行它会更容易(即在foo中保留X.singleton_class。)这可能需要更少的工作量并且会减少意外,所以没有一个人将需要编写代码来处理Class实例与其他实例不同。

可能无关紧要

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

答案 1 :(得分:3)

这不是你问题的答案,但可能有用。要考虑的两件事可能会有所帮助:

    当你想到在其他场景中如何使用meta前缀时,
  • 元类对于这里发生的事情来说并不是一个好名字。您将在其他文档中看到的 eigenclass 可能是一个更好的名称,意思是“对象自己的类”
  • 这不仅仅是具有本征类的类,每个对象都有

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

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

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

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只在需要时创建了本征类。我认为“元类”这个名字确实是你混淆的一部分原因,因为你期望一个“元类”来保存它实际上没有的某种信息。

答案 2 :(得分:2)

原因归结为self。对于任何给定的方法,self是方法定义的对象的实例。

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

答案 3 :(得分:2)

根据The Ruby Programming Language类方法,在与该类同名的类的实例上实际上是单例方法。

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

此处方法self.bar可以在Foo类型的实例Class 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 / singleton / metaclass方法

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

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

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

虽然在概念上错了但我们可以假设类是这个上下文中的对象,以便更好地掌握。

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

在Obj-C中,元类将作为包含有关类meta的类的信息的类进行挽救。并且继承的原则也适用于元类,超类是它的classe的超级类的元类并且爬上去,直到它到达基础对象,谁的元类本身就是元类。可以对此进行更多阅读here