K -
K -

Reputation: 209

Constants vs instance variable

When would you use a constant as opposed to a class instance variable? They both have the same scope. Examples would help.

Upvotes: 0

Views: 3753

Answers (3)

7stud
7stud

Reputation: 48589

About a constant and an instance variable having the same scope:

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.

Now for some jargon:

  1. An instance variable attaches itself to whatever object is self at the time the instance variable is created.

  2. An instance variable is looked up in whatever object is self at the time the instance variables is summoned.

Inside initialize(), a Dog instance is self. At the toplevel, self is an Object called 'main'. That should allow you to figure out the results above.

@variables attach themselves to instances, while methods attach themselves to a Class. Instances of the same class share the methods in the class, but instances of a class do not share @variables--each instance has its own @variables; two instances don't even have to have the same @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]

Local variables, e.g. 'x', 'y', are for storing values that can change.

Constants are for storing values that you don't want to change.

@variables are for attaching values to an instance. If you want an @variable to be constant, then don't define a setter method, although that is not foolproof because ruby permits a programmer to violate privacy if they wish.

Upvotes: 0

tompave
tompave

Reputation: 12402

Consider this class:

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

Here, NUMBER_OF_LEGS is a constant, @name is an instance variable (with a getter method), and @dog_counter is what's called a class instance variable.

In Ruby everything is an object, even classes, and as such they can have their own instance variables.

Look at how we could use this class:

dog = Dog.new('Chewbacca')
dog.name
# => 'Chewbacca'

dog.legs
# => 4
Dog::NUMBER_OF_LEGS
# => 4

That's fine, but we do not have a direct interface to access @dog_counter. The only way to do something with it is to use introspection methods:

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

We can do better than that. Look at this other implementation:

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

Now we have defined a class accessor (setter and getter), and we're also incrementing it with each new instance. The interface is simple:

Dog.dog_counter
# => 0

dog_1 = Dog.new('Han')
dog_2 = Dog.new('Luke')


Dog.dog_counter
# => 2
dog_2.class.dog_counter
# => 2

As to proper class variables, they are scoped on the class and can be accessed by instances.

The big problem, however, is that they are shared between all classes in the same hierarchy. Each class that sets a new value will update it for all its ancestors and descendants.

For this reason they are generally avoided, and class instance variables are preferred (they are class specific).

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!"

Upvotes: 6

sawa
sawa

Reputation: 168071

They don't have the same scope. A class and its instances refer to the same constant, but not to the same instance variable, given the same name. Constants can also be referred from the namespace of a module, but instance variables cannot.

When you want to access something that would be referred to both from a class method and an instance method, or from some other module, then you need a constant.

You can go against the warnings, but that is not good. When you have something that changes, it is better to use a variable.

Upvotes: 2

Related Questions