官方 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 作为接收者。