什么时候使用常量而不是类实例变量?它们都有相同的作用域。
考虑这个类:
class Dog
NUMBER_OF_LEGS = 4
@dog_counter = 0
attr_reader :name
def initialize(name)
@name = name
end
def legs
NUMBER_OF_LEGS
end
end
这里,NUMBER_OF_LEGS
是常量, @name
是实例变量(带有getter方法),@dog_counter
是所谓的类实例变量。
在Ruby中,任何东西都是对象,甚至是类,因此它们可以有自己的实例变量。
看一下我们如何使用这个类:
dog = Dog.new('Chewbacca')
dog.name
# => 'Chewbacca'
dog.legs
# => 4
Dog::NUMBER_OF_LEGS
# => 4
这很好,但是我们没有有直接访问@dog_counter
的接口。使用它的唯一方法是使用自省方法:
dog.class.instance_variable_get(:@dog_counter)
# => 0
dog.class.instance_variable_set(:@dog_counter, 1)
dog.class.instance_variable_get(:@dog_counter)
# => 1
dog.class.instance_eval { @dog_counter = 10 }
dog.class.instance_variable_get(:@dog_counter)
# => 10
我们可以做得更好。看看另一个实现:
class Dog
@dog_counter = 0
attr_reader :name
class << self
attr_accessor :dog_counter
end
def initialize(name)
@name = name
self.class.dog_counter += 1
end
end
现在我们已经定义了一个类的访问器(setter和getter),并且每个新实例都对它进行递增。界面很简单:
Dog.dog_counter
# => 0
dog_1 = Dog.new('Han')
dog_2 = Dog.new('Luke')
Dog.dog_counter
# => 2
dog_2.class.dog_counter
# => 2
对于适当的类变量,它们的作用域在类上,并且可以由实例访问。
然而,最大的问题是它们在同一层次结构中的所有类之间是共享的。每个设置新值的类将为其所有祖先和后代更新该值。由于这个原因,它们通常被避免使用,而类实例变量是首选的(它们是特定于类的)。
class Scientist
@@greet = "Hello, I'm a Scientist!"
def greet
@@greet
end
end
class Biologist < Scientist
@@greet = "Hello, I'm a Biologist!"
end
class Physicist < Scientist
@@greet = "Hello, I'm a Physicist!"
end
class ParticlePhysicist < Physicist
@@greet = "Hello, I'm a ParticlePhysicist!"
end
biologist = Biologist.new
biologist.greet
# => "Hello, I'm a ParticlePhysicist!"
它们没有相同的作用域。一个类和它的实例引用相同的常量,但不引用给定相同名称的相同实例变量。也可以从模块的命名空间引用常量,但不能引用实例变量。
当你想要访问一个既可以从类方法又可以从实例方法,或者从其他模块引用的东西时,你需要一个常量。
你可以违背这些警告,但这并不好。
关于具有相同作用域的常量和实例变量:
C = 10
class Dog
def initialize
puts C #=>10
@x = 1
puts @x #=>1
end
end
d = Dog.new
puts C #=>10
p @x #=>nil It sure doesn't look like @x has the same scope as C.
…
@x = 1
class Dog
C = 10
def initialize
puts C #=>10
p @x #=>nil Here @x does not have the same scope as C.
end
end
d = Dog.new
puts @x #=>1
puts C #=>Error uninitialized constant. Here C does not have the same scope as @x.
现在是一些术语:
实例变量在创建实例变量时将自己附加到任何对象的self上。
实例变量在调用实例变量时在对象的self中查找
在initialize()中,Dog实例是self。在顶层,self是一个名为"main"的对象。这应该能让你算出上面的结果。
@variables将自己附加到实例,而方法将自己附加到类。同一个类的实例共享类中的方法,但类的实例不共享@变量——每个实例都有自己的@变量;两个实例甚至不必有相同的@变量:
class Dog
attr_accessor :x, :y, :z
def initialize
end
end
d1 = Dog.new
d1.x = 10
d2 = Dog.new
d2.y = 1
d2.z = 2
p d1.instance_variables
p d2.instance_variables
--output:--
[:@x]
[:@y, :@z]
局部变量,例如:'x', 'y'用于存储可更改的值。
常量用于存储您不想更改的值。
@variables用于将值附加到实例。如果您希望@变量是常量,那么就不要定义setter方法,尽管这不是万无一失的,因为ruby允许程序员在希望的情况下侵犯隐私。