Maintaining per-class stats with instance variables of class objects
The problem Let's say you have a class and you want to keep track of number of its instances. Using a class variable may be the first thing that come to your mind. Consider this example: class Car @@total_count = 0 def initialize @@total_count += 1 end def self.total_count ...
The problem
Let's say you have a class and you want to keep track of number of its instances. Using a class variable may be the first thing that come to your mind.
Consider this example:
class Car @@total_count = 0 def initialize @@total_count += 1 end def self.total_count @total_count end end c1 = Car.new c2 = Car.new puts "Number of cars: #{Car.total_count}" # => Number of cars: 2
It looks good and all until someone creates a new class that inherits from your class:
class Hybrid < Car end h = Hybrid.new
You come back to check your code, and now the number of cars changed:
puts "Number of cars: #{Car.total_count}" # => Number of cars: 3
@@total_count increased after creating a new instance of Hybrid because class variables are shared between a class and its sub-classes.
You could say a Hybrid car is_a Car, so the number of cars increased is totally understandable. But what if you don't want that? What if you want to protect a class stats from being changed by its sub-classes?
The solution
In Ruby, everything is object, even class. Classes are just special objects. In fact, every class is an instance of a class called Class:
Car.instance_of? Class # => true
Because a class is an object ( we'll call it a class object (object of class Class) ), so it can have its own instance variables, which only accessible by the class object itself. Therefore, we could use class object's instance variables to store stats about itself.
(Note: class object's instance variables are totally different from instance variables of its instances.) (Example about this at footnote.)
class Car # define getter for the class object def self.total_count # @total_count is instance variable of the class object @total_count ||= 0 end # define setter for the class object def self.total_count= value @total_count = value end def initialize self.class.total_count += 1 end end c1 = Car.new c2 = Car.new puts "Number of cars: #{Car.total_count}" # => Number of cars: 2
And now, when someone creates a subclass inherits from your class, that subclass will have its own instance variable @total_count, will have its own stats:
class Hybrid < Car end h = Hybrid.new puts "Number of cars: #{Car.total_count}" # => Number of cars: 2 puts "Number of hybrid: #{Hybrid.total_count}" # => Number of cars: 1
Note: class object's instance variables are totally different from instance variables of its instances.
class Car attr_writer :total_count # line 2 def total_count @total_count #line 4 end def self.total_count @total_count ||= 0 # line 7 end end
- @total_count on line 7 is instance variable of class object Car
- @total_count on line 2 and 4 is instance variable of instances of class Car
Source: The Well-grounded Rubyist, 2nd Edition (p143) by David Black
(I rephrased according to my understanding.)