如果您想报告此常见问题解答中的错误或提出改进建议,请访问我们的 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
表示法。
include
和 extend
有什么区别?
本节或其中部分内容可能已过时或需要确认。
include
将一个模块混合到一个类或另一个模块中。来自该模块的方法以函数样式调用(没有接收者)。
extend
用于将模块包含在对象(实例)中。模块中的方法成为对象中的方法。
self
是什么意思?
self
是当前正在执行的接收者,即应用方法的对象。函数式方法调用意味着 self
作为接收者。