目录 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

官方 Ruby 常见问题解答

如果您想报告此常见问题解答中的错误或提出改进建议,请访问我们的 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

类和模块有什么区别?

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

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

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

一个模块不能从任何东西继承。

你可以继承模块吗?

不可以。但是,可以将一个模块包含在一个类或另一个模块中,以模拟多重继承(mixin 工具)。

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

给我一个 mixin 的例子

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 表示法。

includeextend 有什么区别?

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

include 将一个模块混合到一个类或另一个模块中。来自该模块的方法以函数样式调用(没有接收者)。

extend 用于将模块包含在对象(实例)中。模块中的方法成为对象中的方法。

self 是什么意思?

self 是当前正在执行的接收者,即应用方法的对象。函数式方法调用意味着 self 作为接收者。