1

我无法理解红宝石中的特征类或单例类的概念。我读了很多,特征类是一个类的类。这对我来说没有意义,因为对我来说一个类的类实际上Class是所有类实际上都是类的实例Class

我不太明白的另一件事是以下陈述:类方法实际上是类 eigenclass 的实例方法。特征类可以通过这种方式访问​​:

YourClass = Class.new
class << YourClass
  def class_method
  end
end

但是,如果 eigenclass 确实是 YourClass 类(即Class),那么前面的代码不应该打开该类Class并向其添加实例方法class_method,使其可供所有未来实例访问(即在未来)?

我其实有点觉得单例类和Class. 当你这样做:

class MyClass
end

MyClass.singleton_class

你得到#<Class:MyClass>与输出不同的MyClass.class => Class

那是什么#<Class:MyClass>输出?这与命名空间无关,否则会有两个:Class::MyClass...

我正在寻找对特征类概念的简单而明确的解释,以澄清我的想法。

4

3 回答 3

6

单例类包含特定于单个对象的方法。

对于通用对象,这是一个不错的功能。但是对于课堂来说,这是至关重要的。让我们从对象开始:

对象的单例类

实例方法通常在类中定义。同一类的所有实例共享相同的实例方法。单例类位于对象及其类之间。它允许每个实例拥有自己的一组方法,独立于其他实例。

如果我们有两个类,Foo并且Bar每个类有 2 个实例a,bc, d

class Foo ; end
class Bar ; end

a = Foo.new #=> #<Foo:0x00007fc280963008>
b = Foo.new #=> #<Foo:0x00007f8319016b18>
c = Bar.new #=> #<Bar:0x00007fa66c8d7290>
d = Bar.new #=> #<Bar:0x00007f94d5106ac8>

你会有这样的类结构:(简化,不包括模块)

object          singleton class              class    superclass   ...

  a ── #<Class:#<Foo:0x00007fc280963008>> ─┐
                                           ├─ Foo ─┐
  b ── #<Class:#<Foo:0x00007f8319016b18>> ─┘       │
                                                   ├─ Object ── BasicObject
  c ── #<Class:#<Bar:0x00007fa66c8d7290>> ─┐       │
                                           ├─ Bar ─┘
  d ── #<Class:#<Bar:0x00007f94d5106ac8>> ─┘

Ruby 懒惰地创建这些单例类,例如在调用singleton_class.

所以在定义方法a.hello的时候,并不是存放在a的类Foo中,而是存放在a的单例类中:

def a.hello
  'hello from a'
end

a.method(:hello).owner
#=> #<Class:#<Foo:0x00007fc280963008>>  <-- a's singleton class

因此b,即使两者都是Foo实例,也看不到该方法:

b.hello #=> NoMethodError: undefined method `hello'

b我们甚至可以在不干扰的情况下定义一个同名的方法a

def b.hello
  'hello from b'
end

b.method(:hello).owner
#=> #<Class:#<Foo:0x00007f8319016b18>>  <-- b's singleton class

a.hello #=> "hello from a"
b.hello #=> "hello from b"

我们还可以定义一个泛型helloinFoo并在每个实例级别上覆盖它:(您通常不这样做,但这是可能的)

class Foo
  def hello
    'hello'
  end
end

def a.hello
  "#{super} from a"
end

def b.hello
  "b says #{super.upcase}!"
end

a.hello #=> "hello from a"
b.hello #=> "b says HELLO!"

c = Foo.new
c.hello #=> "hello"

单例类

以上对类尤其重要。每个类都是一个实例Class

Foo.class #=> Class

假设我们想要一个方法Foo.hello,我们将在哪里定义它?

实例方法通常定义在实例的类中,所以我们可以在 的类中定义它Foo

class Class
  def hello
    'Hello from Foo'
  end
end

Foo.hello
#=> "Hello from Foo"

但这将使该方法可用于所有实例Class

Bar.hello
#=> "Hello from Foo"

String.hello
#=> "Hello from Foo"

最好有一个Foo实例专有的地方。那个地方是Foo的单例类:

def Foo.hello
  'Hello from Foo'
end

或者

class Foo
  def self.hello       # <-- self is Foo, so this is just "def Foo.hello"
    'hello from Foo'
  end
end

就像a.hello上面一样,此方法仅适用于Foo

Foo.hello #=> "hello from Foo"
Bar.hello #=> NoMethodError

我们将这些方法称为类方法,但它们实际上只是单例类的实例方法:

Foo.method(:hello).owner
#=> #<Class:Foo>   <-- Foo's singleton class

Foo.method(:hello).unbind == Foo.singleton_class.instance_method(:hello)
#=> true

如果您将类的单例方法与对象的单例方法进行比较,您会发现它们是相同的。这是因为在 Ruby 中,类也是对象,并且所有对象的工作方式都是一样的。

于 2020-04-23T07:41:57.540 回答
1

我认为您将 eigenclass 的概念与 Ruby 类实例的概念混为一谈,有点偏离了轨道。它们并不完全相同。更正确的说法是 Ruby 在内部将类定义实现为两个对象。

这很容易证明:

$ irb
> ObjectSpace.count_objects[:T_CLASS]
 => 1285 
> Foo = Class.new { def instance_method; end }
 => Foo 
> ObjectSpace.count_objects[:T_CLASS]
 => 1287

在内部,每个对象都有一个klass指针,从表面上看,它指向对象所属的类。Integer但是,每个对象(除了某些原语,如FloatSymbol)也有一个特征类实例,这就是klass指针实际指向的内容。在类的情况下,klass指针实际上并不指向Class类,而是指向作为类定义一部分的单例对象,它包含保存类方法的方法表。

正如 anothermh 提供的链接所解释的,“普通”类对象的方法表包含该类的所有实例方法,而 eigenclass 对象的方法表包含该类的所有类方法。这是防止类的任何实例访问所有类的类方法的机制Class

现在, eigenclass (eigen是德语中“我自己的”的意思)是类的类,这就是为什么它也被称为metaclass。(查看源代码Class::new,您将看到对 的调用rb_make_metaclass。)

这就是为什么调用MyClass.singleton_class返回#Class:MyClass而不是Class调用的原因MyClass.class。这种语法类似于p Foo.new返回类似的东西#<Foo:0x00007f9c0d190400>,它是类和指向实例的指针。,#Class:MyClassMyClass指向实例的指针。因此,这描述了对MyClass的元类或 的类的引用MyClassMyClass不要将这与什么类是实例相混淆,当然,它是Class.

如果你好奇,klasseigenclass 实例中的指针实际上指向它自己。MyClass.singleton_class.singleton_classReturns的事实证明了这一点#Class:#Class:MyClass

有关更全面的概述,请参阅Demystifying Ruby Singleton Classes。要查看源代码中发生的情况,请参阅The Ruby Hacking Guide:Chapter 4。最后,Ruby Under a Microscope是全面深入了解 Ruby 内部结构的绝佳资源。

[编辑以将一些讨论纳入评论]

于 2020-04-23T03:16:06.450 回答
0

Eigenclass不再是 Ruby 世界中使用的名称,因为 Ruby 官方Object#singleton_class在我不知道是哪个版本(对不起)中引入了一个方法。

Ruby 中的每个对象,作为“普通”对象或类,甚至是单例类,都有自己的单例类。

单例类是一个类。

Object.new.singleton_class.is_a?(Class)  #=> true

单例类有并且只有一个实例,它是您调用的对象singleton_class。例如foo.singleton_classis的唯一实例foo

Ruby 允许您向单个对象添加方法。

a = Object.new
b = Object.new

def a.foo
  'foo'
end

a.foo  #=> "foo"
b.foo  #=> raises NoMethodError 

但是所有的实例方法都应该定义在一个类中,那么a.foo定义在哪个类中呢?答案是a.singleton_class。因为a.singleton_class只有一个实例即a,所以实例方法foo只能调用 on a,不能调用 on b,尽管它们属于同一类型。

至于类的单例类,它们的目的是存储“普通类”的类方法,或者如果你稍微扭曲一下大脑,就是绑定到各个类实例的实例方法。

你不觉得 Ruby 的对象模型一致且美观吗?

于 2020-04-23T05:14:34.797 回答