常量vs实例变量



什么时候使用常量而不是类实例变量?它们都有相同的作用域。

考虑这个类:

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.

现在是一些术语:

  1. 实例变量在创建实例变量时将自己附加到任何对象的self上。

  2. 实例变量在调用实例变量时在对象的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允许程序员在希望的情况下侵犯隐私。

最新更新