Class Variables

Class methods may remind you of the class variables you used previously (that is, variables whose names begin with @@). You may recall that you used class variables in a simple adventure game (see 2adventure.rb in Attribute Readers and Writers) to keep a tally of the total number of objects in the game; each time a new Thing object was created, 1 was added to the @@num_things class variable:

class Thing
   @@num_things = 0

   def initialize( aName, aDescription )
      @@num_things +=1
   end

end

Unlike an instance variable (that is, a variable that belongs to a specific object created from a class), a class variable must be given a value when it is first declared:

@@classvar = 1000     # class variables must be initialized

Initialization of either instance or class variables within the body of the class affects only the values stored by the class itself. Class variables are available both to the class itself and to the objects created from that class. However, each instance variable is unique; each object has its own copy of any instance variables—and the class itself may also have its own instance variables.

To understand how a class may have instance variables, refer to the class_methods2.rb program. This defines a class containing one class method and one instance method:

class_methods2.rb

class MyClass
    @@classvar = 1000
    @instvar = 1000

    def MyClass.classMethod
        if @instvar == nil then
            @instvar = 10
        else
            @instvar += 10
        end

        if @@classvar == nil then
            @@classvar = 10
        else
            @@classvar += 10
        end
    end

    def instanceMethod
        if @instvar == nil then
            @instvar = 1
        else
            @instvar += 1
        end

        if @@classvar == nil then
            @@classvar = 1
        else
            @@classvar += 1
        end

    end

    def showVars
        return "(instance method) @instvar = #{@instvar}, @@classvar = #{@@classvar}"
    end

    def MyClass.showVars
        return "(class method) @instvar = #{@instvar}, @@classvar = #{@@classvar}"
    end

end

Notice that it declares and initializes a class variable and an instance variable, @@classvar and @instvar, respectively. Its class method, classMethod, increments both these variables by 10, while its instance method, instanceMethod, increments both variables by 1. Notice that I have assigned values to both the class variable and the instance variable:

@@classvar = 1000
@instvar = 1000

I said earlier that initial values are not normally assigned to instance variables in this way. The exception to the rule is when you assign a value to an instance variable of the class itself rather than to an object derived from that class. The distinction should become clearer shortly.

I’ve written a few lines of code that create three instances of MyClass (the ob variable is initialized with a new instance on each turn through the loop) and then call both the class and instance methods:

for i in 0..2 do
   ob = MyClass.new
   MyClass.classMethod
   ob.instanceMethod
   puts( MyClass.showVars )
   puts( ob.showVars )
end

The class method, MyClass.showVars, and the instance method, showVars, display the values of @instvar and @@classvar at each turn through the loop. When you run the code, these are the values displayed:

(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

You may need to look at these results carefully in order to see what is going on here. In summary, this is what is happening: The code in both the class method, MyClass.classMethod, and the instance method, instanceMethod, increments both the class and instance variables, @@classvar and @instvar.

You can see clearly that the class variable is incremented by both these methods (the class method adds 10 to @@classvar whenever a new object is created, while the instance method adds 1 to it). However, whenever a new object is created, its instance variable is initialized to 1 by the instanceMethod. This is the expected behavior since each object has its own copy of an instance variable, but all objects share a unique class variable. Perhaps less obvious is that the class itself also has its own instance variable, @instvar. This is because, in Ruby, a class is an object and therefore can contain instance variables, just like any other object. The MyClass variable, @instvar, is incremented by the class method MyClass.classMethod:

@instvar += 10

When the instance method, showVars, prints the value of @instvar, it prints the value stored in a specific object, ob; the value of ob’s @instvar is initially nil (not the value 1,000 with which the MyClass variable @instvar was initialized), and this value is incremented by 1 in instanceMethod.

When the class method, MyClass.showVars, prints the value of @instvar, it prints the value stored in the class itself (in other words, MyClass’s @instvar is a different variable from ob’s @instvar). But when either method prints the value of the class variable, @@classvar, the value is the same.

Just remember that there is only ever one copy of a class variable, but there may be many copies of instance variables. If this is still confusing, take a look at the inst_vars.rb program:

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

This time, instead of creating a new object instance at each turn through the loop, you create a single instance (ob) at the outset. When the ob.instanceMethod is called, @instvar is incremented by 1.

Here I’ve used a little trick to look inside the class and method and retrieve the value of @instvar using Ruby’s instance_variable_get method (I’ll return to this when I cover dynamic programming in Chapter 20):

puts( "MyClass @instvar= #{MyClass.instance_variable_get(:@instvar)}" )
puts( "ob @instvar= #{ob.instance_variable_get(:@instvar)}" )

Because you only ever increment the @instvar that belongs to the object ob, the value of its @instvar goes up from 1 to 3 as the for loop executes. But the @instvar that belongs to the MyClass class is never incremented; it remains at its initial value of 1,000:

1000
--------------
MyClass @instvar= 1000
ob @instvar= 1
MyClass @instvar= 1000
ob @instvar= 2
MyClass @instvar= 1000
ob @instvar= 3

But now let’s uncomment this line:

MyClass.classMethod

This calls a class method that increments @instvar by 10. This time when you run the program, you see that, as before, the @instvar variable of ob is incremented by 1 on each turn through the loop, while the @instvar variable of MyClass is incremented by 10:

1000
--------------
MyClass @instvar= 1010
ob @instvar= 1
MyClass @instvar= 1020
ob @instvar= 2
MyClass @instvar= 1030
ob @instvar= 3
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.198.59