类变量
类方法可能会让你想到类变量(也就是名称以 @@
开头的变量)。你可能还记得我们之前在一个简单的冒险游戏中使用了类变量(参见第 2 章中的 2adventure.rb)来记录游戏中对象的总数; 每次创建一个新的 Thing 对象时,都会在 @@num_things
类变量中增加 1:
class Thing
@@num_things = 0
def initialize(aName, aDescription)
@@num_things +=1
end
end
与实例变量(在从类派生的对象中)不同,类变量必须在首次声明时给出一个值:
@@classvar = 1000 # class variables must be initialized
在类内初始化实例或类变量只会影响类本身存储的值。类变量(class variable)既可用于类本身,也可用于从该类创建的对象。但是,每个实例变量都是唯一的;每个对象都有属于自己的任何实例变量的副本 - 而类本身也可能有自己的实例变量。
类变量、实例变量以及方法的总结
实例变量以@
开头: @myinstvar # instance variable类变量以 @@
开头: @@myclassvar # class variable实例方法由以下定义:def
<MethodName> def anInstanceMethod # some code end类方法则由以下定义:def
<ClassName>.<MethodName> def MyClass.aClassMethod # some code end要了解一个类怎样才会拥有实例变量,请查看 class_methods2.rb 程序。这里声明并初始化了一个类变量和一个实例变量:
@@classvar = 1000
@instvar = 1000
它定义了一个类方法 classMethod
,将这两个变量递增 10,还有一个实例方法 instanceMethod
,将两个变量递增 1。请注意,我还为实例变量 @instvar
赋了值。我之前说过,初始值通常不会以这种方式分配给实例变量。该规则的例外是将值赋给类本身的实例变量,而不是从该类派生的对象的实例变量。不久之后,这个区别将变得更加明显。
我编写了几行代码来创建 MyClass 类的三个实例(ob
变量在每个循环中初始化一个新实例),然后调用类和实例方法:
for i in 0..2 do
ob = MyClass.new
MyClass.classMethod
ob.instanceMethod
puts( MyClass.showVars )
puts( ob.showVars )
end
我还编写了另一个类方法 MyClass.showVars
和一个实例方法 showVars
,以便在每个循环中显示 @instvar
和 @@classvar
的值。当你运行代码时,将会显示的值:
(class method) @instvar = 1010, @@classvar = 1011
(instance method) @instvar = 1, @@classvar = 1011
(class method) @instvar = 1020, @@classvar = 1022
(instance method) @instvar = 1, @@classvar = 1022
(class method) @instvar = 1030, @@classvar = 1033
(instance method) @instvar = 1, @@classvar = 1033
你可能需要仔细查看这些结果才能看到发生了什么。总之,这就是发生的事情:类方法 MyClass.classMethod
和实例方法 instanceMethod
中的代码都在递增类和实例变量,@@classvar
和 @instvar
。
你可以清楚地看到类变量通过这两种方法都在递增(无论何时创建新对象,类方法都会向 @@classvar
添加 10,实例方法为它添加 1)。但是,每当创建新对象时 instanceMethod
都会将其实例变量初始化为 1。这是预期的行为 - 因为每个对象都有自己的实例变量的副本,但所有对象共享一个唯一的类变量。
也许不太明显的是,类本身也有自己的实例变量 @instvar
。这是因为,在 Ruby 中,类也是一个对象,因此可以包含实例变量,就像任何其它对象一样。MyClass 变量 @instvar
由类方法 MyClass.classMethod
递增:
@instvar += 10
注意当实例方法 showVars
打印 @instvar
的值时,它打印存储在特定对象 ob
中的值; ob
的 @instvar
的值最初是 nil
(并非像 MyClass 的变量 @instvar
一样初始值为 1000)并且此值在 instanceMethod
中递增 1。
当类方法 MyClass.showVars
打印 @instvar
的值时,它打印存储在类本身中的值(换句话说,MyClass 的 @instvar
与来自 ob
的 @instvar
是不同的变量)。但是当任一方法打印出类变量 @@classvar
的值时,值是一样的。
请记住,只会有一个类变量的副本,但可能会有许多实例变量的副本。如果这仍然令人困惑,请看看 inst_vars.rb 程序:
class MyClass
@@classvar = 1000
@instvar = 1000
def MyClass.classMethod
if @instvar == nil then
@instvar = 10
else
@instvar += 10
end
end
def instanceMethod
if @instvar == nil then
@instvar = 1
else
@instvar += 1
end
end
end
ob = MyClass.new
puts MyClass.instance_variable_get(:@instvar)
puts( '--------------' )
for i in 0..2 do
# MyClass.classMethod
ob.instanceMethod
puts("MyClass @instvar=#{MyClass.instance_variable_get(:@instvar)}")
puts("ob @instvar= #{ob.instance_variable_get(:@instvar)}")
end
这一次,我们在一开始就创建了一个实例(ob
),而不是通过循环每次创建一个新的对象实例。当 ob.instanceMethod
调用时,@instvar
增加 1。
在这里,在类和方法中我使用了一个小技巧,使用 Ruby 的 instance_get_variable
方法获取 @instvar
的值:
puts("MyClass @instvar=#{MyClass.instance_variable_get(:@instvar)}")
puts("ob @instvar= #{ob.instance_variable_get(:@instvar)}")
因为我们只增加属于对象 ob
的 @instvar
,所以当 for
循环执行时,@instvar
的值从 1 上升到 3。但是属于 MyClass 类的 @instvar
永远不会增加; 它保持在初始值(1000)…
1000
--------------
MyClass @instvar= 1000
ob @instvar= 1
MyClass @instvar= 1000
ob @instvar= 2
MyClass @instvar= 1000
ob @instvar= 3
但现在,取消掉这一行的注释…
MyClass.classMethod
现在调用一个类方法,它将 @instvar
增加 10。这次当你运行程序时,你会看到,像以前一样,ob
的 @instvar
变量在每次循环中增加 1 而 MyClass 的 @instvar
变量则会增加 10 …
1000
--------------
MyClass @instvar= 1010
ob @instvar= 1
MyClass @instvar= 1020
ob @instvar= 2
MyClass @instvar= 1030
ob @instvar= 3
一个类是一个对象
要理解这一点,只需记住一个类是一个对象(实际上,它是Class
类的一个实例!)。MyClass ‘类对象’(class object)有自己的实例变量(@instvar
),就像 ob
对象有自己的实例变量(在这里,也恰好称为 @instvar
)。实例变量对于对象实例始终是唯一的 - 因此没有两个对象(甚至像 MyClass 这样恰好是一个类的对象!)可以共享一个实例变量。