【问题标题】:Class-objects, singleton classes类对象,单例类
【发布时间】:2012-01-27 17:00:45
【问题描述】:

我在 ruby​​ 中玩元编程,我有一个问题。我有一堂课:

class Klass
  class << self
    @x = "yeah"
  end
end
b = Klass.new
a = class << Klass; self; end
a.instance_eval "@x"           #=> yeah
Klass.instance_eval "@x"       #=> nil

为什么?在变量a 我有一个单例类,对吧?而Klass.instance_eval exec 在单例类的上下文中:

Klass.instance_eval "def yeah; puts 10; end"
Klass.yeah                     #=> 10

另外,解释器中的Klass 指向类的上下文,是吗? a 指向单例类的上下文? 哪个表示a.class_evala.instance_eval?我愿意:

a.instance_eval "def pops; puts 0; end"
a.class_eval "def popsx; puts 1; end"
a.pops                         #=> 0
a.popsx                        # FAIL
Klass.pops                     # FAIL
Klass.popsx                    #=> 1
b.pops; b.popsx                # DOUBLE FAIL

我不明白这一点。谢谢!

【问题讨论】:

  • 通过将“eigenclass”替换为官方术语“singleton class”来编辑问题。

标签: ruby oop metaprogramming


【解决方案1】:

很难完全回答你的问题(关于 Ruby 的类模型的详细解释,请查看Dave Thomas' excellent presentation),但是:

使用 class_eval,您实际上定义了实例方法 - 就好像您在类的主体中一样。例如:

class Klass
end

Klass.class_eval do
  def instance_method
    puts 'instance method'
  end
end

obj = Klass.new
obj.instance_method  # instance method

使用 instance_eval,您实际上定义了类方法 - 就好像您在给定对象的单例(特征类)类的主体中(注意,在 Ruby 中类也是对象)。例如:

Klass.instance_eval do
  def class_method
    puts 'class method'
  end
end

Klass.class_method  # class method

在你的情况下:

Klass.instance_eval "@x" 不起作用,因为@x 不是 Klass 的一部分,它是 Klass 的单例类的一部分:

class Klass
  class << self
    @x = "yeah"
  end

  puts @x
end

# prints nothing

a.instance_eval "@x" 工作正常,因为您在与定义 @x 实例变量的 Klass 类的单例类连接的 a 单例类的上下文中评估“@x”。这个例子可以说明单例类是如何相互连接的:

class Foo
end

f = class << Foo; self; end
g = class << Foo; self; end

f.instance_eval "def yeah; puts 'yeah'; end"

g.yeah  # yeah

g.instance_eval "def ghee; puts 'ghee'; end"

f.ghee  # ghee

Klass.instance_eval "def yeah; puts 10; end" 定义了一个“普通”类方法。因此Klass.yeah 工作正常(参见前面示例中的Klass.class_method)。

a.instance_eval "def pops; puts 0; end"a 单例类上定义了一个类方法。所以a.pops其实就是调用pops类的方法(又是调用Klass.class_method)。

a.popsx 不起作用,因为您首先必须创建 a 的实例才能对其调用 popsx(但无法创建单例类的新实例)。

Klass.pops 不起作用,因为 Klass 的单例类中没有定义任何 pops 方法(pops 定义在 a 的单例类中)。

Klass.popsx 有效,因为使用a.class_eval "def popsx; puts 1; end" 您已经定义了 popsx 实例方法,然后您可以在 Klass 对象上调用该方法。在某种程度上,它类似于这个例子:

class Other
end

o = Other.new

Other.class_eval "def yeah; puts 'yeah'; end"

o.yeah  # yeah

希望对你有帮助。

【讨论】:

    【解决方案2】:

    首先,虽然 eigentclass 似乎被某些人使用 singleton class 是更常见的术语。 Singleton 类包含 Ruby 中对象的特定于对象的行为。除了这个单例类所属的原始对象之外,您不能创建该类的其他实例。

    谈到在不同类型的 eval 中定义方法 this article 介绍了 instance_evalclass_eval 中定义的方法的好规则:

    Use ClassName.instance_eval to define class methods.
    Use ClassName.class_eval to define instance methods.
    

    这几乎描述了这种情况。

    关于作为 Class 类的实例的类、作为 Class 类的子类的单例类以及其他一些疯狂的东西(与问题没有太大关系)有很多文章。但是由于您的问题可以很容易地应用于常规对象及其类(并且它使事情更容易解释),所以我决定将其全部删除(不过,您仍然可以在答案的修订历史中看到这些内容)。 em>

    让我们看看常规类和该类的实例,看看它们是如何工作的:

    class A; end
    a = A.new
    

    不同类型eval内部的方法定义:

    # define instance method inside class context
    A.class_eval { def bar; 'bar'; end }
    puts a.bar     # => bar
    puts A.new.bar # => bar
    
    # class_eval is equivalent to re-opening the class
    class A
      def bar2; 'bar2'; end
    end
    puts a.bar2     # => bar2
    puts A.new.bar2 # => bar2
    

    定义对象特定的方法:

    # define object-specific method in the context of object itself
    a.instance_eval { def foo; 'foo'; end }
    puts a.foo # => foo
    
    # method definition inside instance_eval is equivalent to this
    def a.foo2; 'foo2'; end
    puts a.foo2 # => foo2
    
    # no foo method here
    # puts A.new.foo # => undefined method `foo' for #<A:0x8b35b20>
    

    现在让我们看看对象a的单例类:

    # singleton class of a is subclass of A
    p (class << a; self; end).ancestors
    # => [A, Object, Kernel, BasicObject]
    
    # define instance method inside a's singleton class context
    class << a
      def foobar; 'foobar'; end;
    end
    puts a.foobar # => foobar
    
    # as expected foobar is not available for other instances of class A
    # because it's instance method of a's singleton class and a is the only
    # instance of that class
    # puts A.new.foobar # => undefined method `foobar' for #<A:0x8b35b20>
    
    # same for equivalent class_eval version
    (class << a; self; end).class_eval do
      def foobar2; 'foobar2'; end;
    end
    puts a.foobar2 # => foobar2
    
    # no foobar2 here as well
    # puts A.new.foobar2 # => undefined method `foobar2' for #<A:0x8b35b20>
    

    现在让我们看看实例变量:

    # define instance variable for object a
    a.instance_eval { @x = 1 }
    
    # we can access that @x using same instance_eval
    puts a.instance_eval { @x } # => 1
    # or via convenient instance_variable_get method
    puts a.instance_variable_get(:@x) # => 1
    

    现在到class_eval中的实例变量:

    # class_eval is instance method of Module class
    # so it's not available for object a
    # a.class_eval { } # => undefined method `class_eval' for #<A:0x8fbaa74>
    
    # instance variable definition works the same inside
    # class_eval and instance_eval
    A.instance_eval { @y = 1 }
    A.class_eval    { @z = 1 }
    
    # both variables belong to A class itself
    p A.instance_variables # => [:@y, :@z]
    
    # instance variables can be accessed in both ways as well
    puts A.instance_eval { @y } # => 1
    puts A.class_eval    { @z } # => 1
    
    # no instance_variables here
    p A.new.instance_variables # => []
    

    现在,如果您将A 类替换为Class 类,将a 对象替换为Klass 对象(在这种特殊情况下只不过是Class 类的实例)我希望你能得到解释你的问题。如果您仍有疑问,请随时询问。

    【讨论】:

    • 非常感谢!另外,我认为您应该从修订版中回来为其他人撰写有关课程的大量文章
    • 我离问题太远了 =) 不过,这个版本中以更结构化的形式呈现了其中最有价值的部分,所以我认为没关系。
    • 另外,你会说俄语吗?我可以通过 gtalk 和你通话吗?
    • @avy,是的,我会说俄语,但我对私人咨询不太感兴趣 =)
    猜你喜欢
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-18
    • 1970-01-01
    • 1970-01-01
    • 2022-06-23
    相关资源
    最近更新 更多