内容 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

官方 Ruby FAQ

如果您希望报告错误或对本 FAQ 提出改进建议,请访问我们的 GitHub 仓库,并提出 issue 或 pull request。

类和模块

类定义可以重复吗?

类定义可以重复。每次定义都会被添加到上一次的定义中。如果一个方法被重新定义,之前的方法将被覆盖并丢失。

有类变量吗?

有。以两个 at 符号(@@)为前缀的变量是类变量,可以在类的实例方法和类方法中访问。

class Entity

  @@instances = 0

  def initialize
    @@instances += 1
    @number = @@instances
  end

  def who_am_i
   "I'm #{@number} of #{@@instances}"
  end

  def self.total
    @@instances
  end
end

entities = Array.new(9) { Entity.new }

entities[6].who_am_i  # => "I'm 7 of 9"
Entity.total          # => 9

然而,你可能应该使用类实例变量

什么是类实例变量?

这里是上一节使用类实例变量重写的示例。

class Entity

  @instances = 0

  class << self
    attr_accessor :instances  # provide class methods for reading/writing
  end

  def initialize
    self.class.instances += 1
    @number = self.class.instances
  end

  def who_am_i
   "I'm #{@number} of #{self.class.instances}"
  end

  def self.total
    @instances
  end
end

entities = Array.new(9) { Entity.new }

entities[6].who_am_i  # => "I'm 7 of 9"
Entity.instances      # => 9
Entity.total          # => 9

在这里,@instances 是一个实例变量。它不属于 Entity 类的某个实例,而是属于 Entity 类对象,它本身是 Class 类的实例。

类实例变量只能在类的类方法中直接访问。

类变量和类实例变量有什么区别?

主要区别在于继承行为:类变量在类及其所有子类之间共享,而类实例变量只属于一个特定的类。

从某种意义上说,类变量可以被视为继承层次结构中的全局变量,并带有全局变量的所有问题。例如,类变量可能会被其任何子类(意外地)重新分配,从而影响所有其他类。

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

或者,祖先类后来可能被重新打开并修改,可能产生令人惊讶的效果。

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

因此,除非你确切地知道你在做什么并且明确需要这种行为,否则最好使用类实例变量。

Ruby 有类方法吗?

类对象的单例方法称为类方法。(实际上,类方法定义在元类中,但这在很大程度上是透明的)。另一种看待方式是说类方法是接收者为类的某个方法。

这一切都归结于你可以调用类方法而不必以该类的实例(对象)作为接收者。

让我们创建一个 Foo 类的单例方法。

class Foo
  def self.test
    "this is foo"
  end
end

# It is invoked this way.

Foo.test  # => "this is foo"

在这个例子中,Foo.test 是一个类方法。

定义在 Class 类中的实例方法可以作为每个(!)类的类方法使用。

什么是单例类?

单例类是一个匿名类,它通过继承与特定对象关联的类来创建。单例类是扩展仅与单个对象相关的功能的另一种方式。

以普通的 Foo 为例。

class Foo
  def hello
    "hello"
  end
end

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

现在,假设我们需要为这个单独的实例添加类级别的功能。

class << foo
  attr_accessor :name

  def hello
    "hello, I'm #{name}"
  end
end

foo.name = "Tom"
foo.hello         # => "hello, I'm Tom"
Foo.new.hello     # => "hello"

我们自定义了 foo,而没有改变 Foo 的特性。

什么是模块函数?

本节或部分内容可能已过时或需要确认。

模块函数是定义在模块中的私有单例方法。实际上,它类似于类方法,因为它可以使用 Module.method 符号来调用。

Math.sqrt(2)  # => 1.414213562

但是,由于模块可以混入类中,所以模块函数也可以在没有前缀的情况下使用(Kernel 的所有函数都可以通过这种方式提供给对象)。

include Math
sqrt(2)  # => 1.414213562

使用 module_function 来使一个方法成为模块函数。

module Test
  def thing
    # ...
  end
  module_function :thing
end

类和模块有什么区别?

模块是方法的集合和常量。它们不能生成实例。类可以生成实例(对象),并具有每个实例的状态(实例变量)。

模块可以混入类和其他模块中。混入的模块的常量和方法会融入该类自己的内容中,从而增强类的功能。然而,类不能混入任何东西。

类可以继承自另一个类,但不能继承自模块。

模块不能继承自任何东西。

可以子类化模块吗?

不可以。但是,模块可以被包含在类或另一个模块中,以模拟多重继承(混入机制)。

这不会生成子类(这需要继承),但确实会生成类和模块之间的 is_a? 关系。

给我一个混入的例子。

模块 Comparable 提供了各种比较运算符(<, <=, ==, >=, >, between?)。它根据通用比较方法 <=> 来定义这些。然而,它本身并不定义 <=>

假设你想创建一个基于动物腿数量进行比较的类。

class Animal
  include Comparable

  attr_reader :legs

  def initialize(name, legs)
    @name, @legs = name, legs
  end

  def <=>(other)
    legs <=> other.legs
  end

  def inspect
    @name
  end
end

c = Animal.new("cat", 4)
s = Animal.new("snake", 0)
p = Animal.new("parrot", 2)

c < s             # => false
s < c             # => true
p >= s            # => true
p.between?(s, c)  # => true
[p, s, c].sort    # => [snake, parrot, cat]

Animal 所要做的就是定义其 <=> 运算符的语义,并混入 Comparable 模块。Comparable 的方法现在与 Animal 的方法无法区分,你的类会突然拥有新的功能。而且,由于许多类都使用相同的 Comparable 模块,你的新类也将共享一致且易于理解的语义。

为什么有两种定义类方法的方式?

你可以在类定义中定义类方法,也可以在顶层定义类方法。

class Demo
  def self.class_method
  end
end

def Demo.another_class_method
end

两者之间只有一个显著的区别。在类定义中,你可以直接引用类的常量,因为常量在作用域内。在顶层,你必须使用 Class::CONST 符号。

include 和 extend 有什么区别?

本节或部分内容可能已过时或需要确认。

include 将一个模块混入一个类或另一个模块。来自该模块的方法以函数式(无接收者)的方式调用。

extend 用于将一个模块混入一个对象(实例)。模块中的方法成为该对象的方法。

self 的意思是?

self 是当前执行的接收者,即应用方法的对象。函数式的方法调用暗示 self 作为接收者。