Ruby类方法与特有类中的方法相比如何?

14

类方法和该类的特殊类(也称元类或eigenclass)中的方法是定义一个东西的两种方式吗?

如果不是,它们有什么区别?

class X
  # class method
  def self.a
    "a"
  end

  # eigenclass method
  class << self
    def b
      "b"
    end
  end
end

X.aX.b在任何方面上有不同的行为吗?

我意识到可以通过打开特殊类来重写或别名化类方法:

irb(main):031:0> class X; def self.a; "a"; end; end
=> nil
irb(main):032:0> class X; class << self; alias_method :b, :a; end; end
=> #<Class:X>
irb(main):033:0> X.a
=> "a"
irb(main):034:0> X.b
=> "a"
irb(main):035:0> class X; class << self; def a; "c"; end; end; end
=> nil
irb(main):036:0> X.a
=> "c"
4个回答

11

这两种方法是等效的。 "eigenclass"版本有助于使用attr_*方法,例如:

class Foo
  @instances = []
  class << self;
    attr_reader :instances
  end
  def initialize
    self.class.instances << self
  end
end

2.times{ Foo.new }
p Foo.instances
#=> [#<Foo:0x2a3f020>, #<Foo:0x2a1a5c0>]

你也可以使用define_singleton_method在类上创建方法:

Foo.define_singleton_method :bim do "bam!" end

1
这是一个关于单例模式的非常出色的答案。 - New Alexandria

6
在Ruby中,实际上不存在类方法这样的东西。因为在Ruby中一切都是对象(包括类),当你使用def self.class_method时,你只是在类Class的实例上定义了一个单例方法。所以回答你的问题,说
class X
  def self.a
    puts "Hi"
  end

  class << self
    def b
      puts "there"
    end
  end
end

X.a # => Hi
X.b # => there

两种说法是同一件事情的两种方式。这两种方法都只是在你的类对象实例中定义的单例(eigen,meta,ghost或任何你想称呼它们的名称)方法,例如你的示例中的X。这个主题是元编程的一部分,如果你已经使用Ruby一段时间了,应该去看一下这个有趣的主题。

如果你对这个主题感兴趣,Pragmatic Programmers有一本很棒的书籍关于元编程,你一定要去看一下。


2
对于“没有类方法这种东西”持不同意见。Smalltalk已经存在很长时间了,也是纯面向对象的,他们一直使用类方法术语。说类方法比“单例”更少令人困惑,因为后者是一种设计模式,而不是语言的属性。 - Syed Ali
1
我可能因为死灵法而获得奖励,但我强烈同意“没有类方法”这一说法。如果你在一个对象的特殊类上定义了一个方法,并且该对象是Dog的实例,那么你不会称呼这个方法为狗的方法。 - ggPeti
如果你使用:private_class_method函数标记一个方法,它肯定会让你认为它是一个类方法。 - chad_

3

又有一个死灵法师来挖掘这个老问题了... 你可能不知道的是,将类方法标记为private(使用私有关键字而不是:private_class_method)与将特异类方法标记为私有是不同的。

class Foo
  class << self
    def baz
      puts "Eigenclass public method."
    end

    private
    def qux
      puts "Private method on eigenclass."
    end
  end
  private
  def self.bar
    puts "Private class method."
  end
end

Foo.bar
#=> Private class method.
Foo.baz
#=> Eigenclass public method.
Foo.qux
#=> NoMethodError: private method `qux' called for Foo:Class
#   from (irb)

以下示例将按照前一个示例的意图工作:
class Foo
  class << self
    def baz
      puts "Eigen class public method."
    end

    private
    def qux
      puts "Private method on eigenclass."
    end
  end
  def bar
    puts "Private class method."
  end
  private_class_method :bar
end
Foo.bar
#=> NoMethodError: private method `bar' called for Foo:Class
#     from (irb)
Foo.baz
#=> Eigen class public method.
Foo.qux
#=> NoMethodError: private method `qux' called for Foo:Class
#     from (irb)

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

z1 = Zen.new
z2 = Zen.new

def z1.say_hello  # Notice that the method name is prefixed with the object name
  puts "Hello!"
end

z1.say_hello    # Output: Hello!
z2.say_hello    # Output: NoMethodError: undefined method `say_hello'…

在上面的例子中,say_hello方法是在Zen类的z1实例中定义的,但不在z2实例中定义。
下面的例子展示了一种不同的定义单例方法的方式,结果相同。
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 方法。
上述两个例子都用于说明单例方法的工作原理。不过,有一种更简单的定义单例方法的方式:使用一个名为 define_singleton_method 的内置方法。
class Zen
end

z1 = Zen.new
z2 = Zen.new

z1.define_singleton_method(:say_hello) { puts "Hello!" }

z1.say_hello    # Output: Hello!
z2.say_hello    # Output: NoMethodError: undefined method `say_hello'…

我们之前学过,类也是对象(内置类Class的实例)。我们也学过类方法。类方法不过是与类对象相关联的单例方法。
再举一个例子:
class Zabuton
  class << self
    def stuff
      puts "Stuffing zabuton…"
    end
  end
end

所有对象都可以有元类。这意味着类也可以有元类。在上面的例子中,class << self 修改了self,使其指向Zabuton类的元类。当一个方法被定义时没有明确的接收者(方法将被定义的类/对象),它会隐式地被定义在当前范围内,也就是当前self的值。因此,stuff方法是在Zabuton类的元类中定义的。上面的例子只是定义类方法的另一种方式。
阅读更多:关于Ruby类的这篇文章

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