class << self 和 self.method 在 Ruby 中有什么区别?哪个更好?

72

这个Ruby Style Guide指出,最好使用self.method_name而不是class method_name。但为什么呢?

class TestClass
  # bad
  class << self
    def first_method
      # body omitted
    end

    def second_method_etc
      # body omitted
    end
  end

  # good
  def self.first_method
    # body omitted
  end

  def self.second_method_etc
    # body omitted
  end
end

有性能问题吗?


23
如果没有附带的解释,接受一种风格建议会很难认真对待,不是吗? - Michael Berkowski
3
请注意,它说“这是我们在GitHub内部使用的Ruby应用程序指南。”也就是说,这是Github为自己定义的样式。这不是正确的Ruby样式的权威指南。 - Michael Berkowski
1
我同意,但似乎是由具有权威技能的熟练Ruby程序员编写的。 - Mich Dart
1
对于来说,self 更易读。此外,这也是一种标准(我从未见过使用 class << 的 gem/app 等)。 - Samy Dindane
3
请不要使用class << self,不要说元类或特别是不要说 Eigenclass。这些都是令人困惑、过时的名称,Ruby有一个适当的名称来代替它们:单例类。可以使用singleton_class.class_eval代替class << self,这样更清晰地表达了正在发生的事情。对于单个方法,可以使用define_singleton_method,这更加明确。 - Max
显示剩余5条评论
6个回答

103

class << self 可以很好地将所有类方法放在同一个块中。如果使用def self.method的形式添加方法,那么除了惯例和愿望之外,并没有保证不会在文件的其他位置藏有额外的类方法。

def self.method 显式地声明了一个方法是类方法,而对于 class << self,你必须自己去找容器。

哪种方式更重要是主观决定,也取决于其他人如何处理这份代码和他们的偏好。


8
我不同意,这不仅仅是一个简单的个人选择。使用<<有代码可读性下降的缺点--请参阅@Flexoid的答案。使用class << self语法和使用代码块的缺点是,如果该块超过了几个方法,那么除非您向上滚动到class << self部分,否则这些方法是类方法并不明显。 - Dmitri Zagidulin
2
这不是 class << self 特有的问题。我见过很多源文件(通常在大型、非平凡项目中),存在深层模块嵌套,直到向上滚动才能清楚地知道一个方法所在的 - Gareth
1
在这种情况下,你可以根据缩进告诉这些方法是类方法。 - weakish
1
“那么除了惯例和一厢情愿的想法之外,就没有保证了。” <-- @Gareth 但是仍然不能保证不会在self << class旁边有额外的self.method声明。 “不要混淆这两种风格”是一种惯例。 - weakish
1
@DmitriZagidulin,不想冒昧,但这个论点很荒谬。它适用于任何情况,例如:“使用protected方法的缺点是,如果你定义了超过几个方法,除非你向上滚动到protected部分,否则无法明确这些方法是受保护的”。 - Andre Figueiredo

32

通常,class << self在元编程中用于将类设置为self以长时间使用。如果我要编写10个方法,我会这样使用:

METHOD_APPENDICES = [1...10]
class << self
  METHOD_APPENDICES.each do |n|
    define_method("method#{n}") { n }
  end
end
这将创建10个方法(method1,method2,method3等),这些方法将只返回该数字。在这种情况下,我会使用 for clarity>,因为在元编程中是至关重要的。在内部散布实际上会使事情不太容易读懂。

如果您只是普通地定义类方法,请使用,因为更多的人可能会理解它。除非您希望您的受众能够理解它,否则没有必要引入元语法。


我认为这是最好的答案。不过,它给出了一个客观理由,即Ruby有两种语法风格来定义类方法。 - 18augst

30

如上所述,两种风格似乎是等效的,但使用class << self可以将类方法标记为privateprotected。例如:

class UsingDefSelf
  def self.a; 'public class method'; end
  private
  def self.b; 'public class method!'; end
end

class UsingSingletonClass
  class << self
    def a; 'public class method'; end
    private
    def b; 'private class method'; end
  end
end

private关键字仅影响实例方法。通过使用单例类,我们定义了该类的实例方法,这些方法会变成包含类的类方法!

我们也可以使用def self将类方法标记为private

class UsingDefSelf
  def self.a; 'private class method'; end
  def self.b; 'private class method!'; end
  private_class_method :a, :b
  # In Ruby 2.1 there is an alternative syntax
  private_class_method def self.c; 'private class method!'; end
end

但我们不能将它们标记为protected,因为没有protected_class_method。(然而,由于类是其单例类的唯一实例,私有类方法和受保护的类方法几乎相同,除了它们的调用语法不同。)

同时,与使用class << self标记private类方法相比,这要困难得多,因为您必须在private_class_method中列出所有方法名称或将private_class_method前缀添加到每个私有类方法定义中。


7
无论您选择哪个都很清楚您要做什么。但是我对此有一些建议。
当只有一个类方法需要定义时,请使用def self.xxx。因为对于仅定义一个方法的情况,增加缩进级别可能会变得凌乱。
当有多个类方法需要定义时,请使用class << self。因为写def self.xxxdef self.yyydef self.zzz肯定会重复。创建这些方法的部分。
当类中的所有方法都是类方法时,您可以使用带有module_functionmodule而不是class。这样,您可以使用def xxx定义模块函数。

6

我认为他们认为self.*更好,因为你可以确定它是一个类方法还是实例方法,而无需向上滚动并搜索class << self字符串。


1
我对其他答案感到惊讶,因为这是唯一明智的方法。如果你写“class << self”,只有当a)你只有少量的方法和b)当前在你的编辑器中可见的“class << self”行时,读者才能看出这些方法是类方法。如果您遵循“def self.thing”约定,也很容易对类和实例方法进行grep操作。Dave Thomas建议将“<< self”保留给需要更改“self”的情况(在块内;例如,在类上调用attr_accessor)。 - Graham Ashton
4
但是在这种情况下,拥有私有方法是否同样会使代码变得“难以理解”?因为它们都缩进在“private”关键字下面。我想在Ruby中,您可以显式调用“private:method”来定义每个方法,但考虑到C语法的工作方式,我认为合理的期望是开发人员知道函数/方法定义可能有上下文环境。 - Alexander Bird

-3

到目前为止,问题和答案只讨论了这两个选项:

class MyClass
  def self.method_name
    ..
  end
end

class MyClass
  class << self
    def method_name
      ..
    end
  end
end

对于类方法,还有第三个选项需要考虑:

class MyClass
  def MyClass.method_name
    ..
  end
end

虽然不太流行且更冗长,但它是最明确的选项。

如果你在 Python 和 Ruby 之间混淆了 self 的行为,这也会让人少些困惑。


我也来自Python背景,其中self用于实例方法,而在Ruby中,self用于类方法。这经常让我感到困惑,因为在Ruby中,“class”是一个“实例”。在Python中,实例方法是def m(self, args):,而在Ruby中是def(args)(只需省略“self”和“:”)。 - weakish
2
请不要使用这种风格,Ruby是一个具有高度见解社区的语言。这使得开发人员可以并排编码而无需过多解释或达成一致。这只是为您自己设定独特的标准。 - Adit Saxena
@AditSaxena 为你的项目/团队制定一个样式指南可以解决这个问题。(顺便说一句,我现在几乎只使用 Ruby 编程,已经习惯了 self,所以不再使用我提出的选项,但了解不同的选项仍然很有好处。) - Dennis

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