class << self idiom in Ruby

1006
<代码>class << self在Ruby中的作用是什么?

42
这个主题有一篇非常好的文章,作者是 Yehuda Katz:http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/ 和 Yugui:http://yugui.jp/articles/846。 - Andrei
4
这里又有一篇超棒的文章:http://www.integralist.co.uk/posts/eigenclass.html - Saman Mohamadi
2
我在模块内看到这个,这会有所不同吗? https://github.com/ruby/rake/blob/master/lib/rake/rake_module.rb - William Entriken
@FullDecent 这并不重要,因为在 Ruby 中,包括模块和类在内的所有东西都是对象。 - Aaron
1
请参见 https://github.com/defunkt/metaid/blob/master/metaid.rb 它是与“清晰地看到元类”相关的 http://viewsourcecode.org/why/hacking/seeingMetaclassesClearly.html - Douglas G. Allen
Ruby-doc文档可以在此处找到:https://ruby-doc.org/core-3.0.2/doc/syntax/modules_and_classes_rdoc.html#label-Singleton+Classes。 - 3limin4t0r
6个回答

1006

首先,class << foo 语法打开了 foo 的单例类(eigenclass)。这使您能够专门为在该特定对象上调用的方法定制行为。

a = 'foo'
class << a
  def inspect
    '"bar"'
  end
end
a.inspect   # => "bar"

a = 'foo'   # new object, new singleton class
a.inspect   # => "foo"
现在来回答问题:class << self 打开了self的单例类,这样方法就可以被重新定义为当前的self对象(在类或模块主体内部是类或模块本身)。通常,这用于定义类/模块("静态")方法:
class String
  class << self
    def value_of obj
      obj.to_s
    end
  end
end

String.value_of 42   # => "42"

这也可以用缩写方式表示:

class String
  def self.value_of obj
    obj.to_s
  end
end

甚至可以更短:

def String.value_of obj
  obj.to_s
end
在函数定义内部,self指的是被调用该函数的对象。在这种情况下,class << self打开了该对象的单例类;其中一个用途是实现一个简单的状态机。
class StateMachineExample
  def process obj
    process_hook obj
  end

private
  def process_state_1 obj
    # ...
    class << self
      alias process_hook process_state_2
    end
  end

  def process_state_2 obj
    # ...
    class << self
      alias process_hook process_state_1
    end
  end

  # Set up initial state
  alias process_hook process_state_1
end

所以,在上面的示例中,每个StateMachineExample实例都将process_hook别名为process_state_1,但请注意,在后者中,它可以重新定义process_hook(仅对self有效,不影响其他StateMachineExample实例),以便变为process_state_2。因此,每当调用者调用process方法(调用可重新定义的process_hook)时,行为会根据所处状态而发生变化。


24
@Jörg:对编辑的点赞(我希望SO提供点赞编辑的功能,哦好吧)。确实,这是更常见的使用class << self的方式,用于创建类/模块方法。我可能会进一步扩展关于class << self的使用,因为那是一个更加惯用的用法。 - C. K. Young
4
使用gsub!替换"eigenclass"为"singleton class",查看即将推出的方法http://redmine.ruby-lang.org/repositories/revision/1?rev=27022。 - Marc-André Lafortune
@Marc-Andre:哇,这是Matz最近做出的一个非常突然的决定(投降?他在整个讨论中似乎非常不情愿)。不过,很好,至少对于今后该如何称呼它已经达成了一致。 - C. K. Young
5
当提到asingleton_class时,会感到困惑,因为在更改inspect之后,a的类是String类的一个唯一变体。如果更改单例String类,将影响所有其他String实例,这更加奇怪的是,如果稍后重新打开String以重新定义inspect,则a仍将拾取新更改。 - Old Pro
1
@OldPro,我仍然更喜欢使用eigenclass这个名称,因为(我相信)Matz也是这样认为的。但是,我猜不能取悦每个人。 - C. K. Young
5
我发现这个表达:“打开一个对象的单例类”——我之前读过很多遍——比较含糊。据我所知,在 Ruby 文档中没有明确定义“打开”一个类,尽管我们都知道它的意思。class <<self 是否比仅仅是在块作用域内将 self 的值设置为单例类更有含义? - Cary Swoveland

50

我找到了一份关于 class << selfEigenclass 和不同类型方法的超简单解释。

Ruby 中有三种可以应用于类的方法:

  1. 实例方法
  2. 单例方法
  3. 类方法

实例方法和类方法在其他编程语言中都有相似的概念。

class Foo  
  def an_instance_method  
    puts "I am an instance method"  
  end  
  def self.a_class_method  
    puts "I am a class method"  
  end  
end

foo = Foo.new

def foo.a_singleton_method
  puts "I am a singletone method"
end

访问Eigenclass(包括单例方法)的另一种方式是使用以下语法(class <<):

可用于访问对象的Eigenclass,从而定义或调用该类别的单例方法。
foo = Foo.new

class << foo
  def a_singleton_method
    puts "I am a singleton method"
  end
end

现在,您可以在此上下文中为self定义一个单例方法,该方法就是类本身Foo:

class Foo
  class << self
    def a_singleton_and_class_method
      puts "I am a singleton method for self and a class method for Foo"
    end
  end
end

10
其实单例方法和类方法是相同的,它们都存在于单例类中。你可以使用foo.singleton_class.instance_methods(false)来检查。 - Damon Yuan
在第一个编码片段中,“singleton”被拼写为“singletone”。 - Quentin Gibson

45
通常情况下,实例方法是全局方法。这意味着它们在定义它们的类的所有实例中都可用。相比之下,单例方法是在单个对象上实现的。
Ruby将方法存储在类中,所有方法都必须与一个类相关联。定义单例方法的对象不是类(它是类的实例)。如果只有类可以存储方法,那么对象如何存储单例方法呢?当创建单例方法时,Ruby自动创建一个匿名类来存储该方法。这些匿名类称为元类,也称为单例类或特异类。单例方法与元类相关联,而元类又与定义单例方法的对象相关联。
如果在单个对象中定义了多个单例方法,则它们都存储在同一个元类中。
class Zen
end

z1 = Zen.new
z2 = Zen.new

class << z1
  def say_hello
    puts "Hello!"
  end
end

z1.say_hello    # Output: Hello!
z2.say_hello    # Output: NoMethodError: undefined method `say_hello'…
在上面的例子中,class << z1将当前的self指向z1对象的元类;然后在元类中定义了say_hello方法。类也是对象(内置类Class的实例)。类方法无非就是与类对象相关联的单例方法。
class Zabuton
  class << self
    def stuff
      puts "Stuffing zabuton…"
    end
  end
end

所有对象都可能有元类,这意味着类也可以有元类。在上面的例子中,class << self 修改了 self,使其指向 Zabuton 类的元类。当定义一个没有明确接收者(方法将被定义的类/对象)的方法时,它会隐式地在当前范围内定义,也就是self的当前值。因此,stuff方法在Zabuton类的元类中定义。以上例子只是定义类方法的另一种方式。个人认为,最好使用def self.my_new_clas_method语法来定义类方法,因为它使代码更易于理解。以上示例是为了让我们了解当我们遇到class << self语法时发生了什么。

更多信息请参见有关Ruby类的此文章


2
在查阅了各种资料后,您的回答真正地解释了一切。非常感谢! - Richard Logwood
完美的解释。谢谢! - otavio1992
我希望有人能给出一个好的例子,说明这个机制在哪里真正需要。我看到的所有例子都是关于foobar或puts "bla"之类的。我无法想象出一个我真正需要这个机制的情况。(我在一些我需要维护的旧代码中遇到了它,除了证明原作者知道class << self,我真的不知道他为什么要这样做。) - MDickten
@MDickten 假设你想要计算一个类被实例化的次数。你可以定义一个类级别的 @count = 0 变量(在任何方法之外)。有两种访问它的方式:def self.count; @count; end 或者 class << self; attr_accessor :count; end。在初始化方法中:self.class.count += 1。在类外部:puts "@count=#{SomeClass.count}"。更复杂的例子可以在 https://pragprog.com/titles/ruby5/programming-ruby-3-2-5th-edition/ 中找到。 - undefined
@MDickten 这个例子有些牵强,因为在这种情况下使用@@count会更容易。我见过最常见的用法是定义类方法。他们使用class << self的技巧将self更改为类的单例类,然后使用简单的defdef m1def m2,...)在单例类中定义单例方法(通常称为类方法)。 - undefined
@MDickten 请参阅https://dev.to/vitaliipaprotskyi/what-does-class-self-actually-do-in-ruby-2on1 - undefined

21

类和对象的作用:

class Hi
  self #=> Hi
  class << self #same as 'class << Hi'
    self #=> #<Class:Hi>
    self == Hi.singleton_class #=> true
  end
end

[在其块的上下文中,它使得 self == thing.singleton_class ].


thing.singleton_class是什么?

hi = String.new
def hi.a
end

hi.class.instance_methods.include? :a #=> false
hi.singleton_class.instance_methods.include? :a #=> true

hi对象从其#singleton_class.instance_methods继承它的#methods,然后再从其#class.instance_methods继承。
这里我们给了hisingleton类实例方法:a。也可以使用class << hi来完成同样的操作。
hi#singleton_class拥有所有hi#class的实例方法,并且可能还有一些其他的(:a在此)。

[thing的实例方法的#class和#singleton_class可以直接应用于thing。当Ruby看到thing.a时,它首先查找thing.singleton_class.instance_methods中的:a方法定义,然后在thing.class.instance_methods中查找]


顺便说一下-他们称对象的singleton类== 元类 == eigenclass


3

单例方法是仅为单个对象定义的方法。

例如:

class SomeClass
  class << self
    def test
    end
  end
end

test_obj = SomeClass.new

def test_obj.test_2
end

class << test_obj
  def test_3
  end
end

puts "Singleton's methods of SomeClass"
puts SomeClass.singleton_methods
puts '------------------------------------------'
puts "Singleton's methods of test_obj"
puts test_obj.singleton_methods

SomeClass的单例方法

测试


test_obj的单例方法

测试_2

测试_3


1
实际上,如果你为你的 Ruby 项目编写任何 C 扩展,那么定义模块方法只有一种方法。
rb_define_singleton_method

我知道这个自己的商业会引发各种其他问题,所以你最好通过搜索每个部分来更好地理解。
先从对象开始。
foo = Object.new

我可以为foo创建一个方法吗?
当然可以。
def foo.hello
 'hello'
end

我该怎么处理它?
foo.hello
 ==>"hello"

只是另一个物体。
foo.methods

你可以获得所有的对象方法以及你新增的那个方法。
def foo.self
 self
end

foo.self

只是foo对象。

尝试看看如果你从其他对象(如Class和Module)中创建foo会发生什么。所有答案的示例都很好玩,但您必须使用不同的想法或概念来真正理解代码编写方式背后发生的事情。所以现在您有很多术语可以去查看。

Singleton、Class、Module、self、Object和Eigenclass都被提出过,但Ruby不会以这种方式命名对象模型。更像元类(Metaclass)。 Richard或__why在这里向您展示了这个想法。 http://viewsourcecode.org/why/hacking/seeingMetaclassesClearly.html 如果这让你震惊了,那么尝试在搜索中查找Ruby对象模型。 我知道有两个YouTube视频是Dave Thomas和Peter Cooper制作的。他们也试图解释这个概念。Dave花了很长时间才明白,所以不要担心。 我还在努力理解它。否则我为什么会在这里呢? 感谢您的问题。 此外,请查看标准库。它有一个Singleton模块,这只是一个FYI。

这很不错。 https://www.youtube.com/watch?v=i4uiyWA8eFk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接