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
3.128.198.59