Maintaining per-class stats with instance variables of class objects
Ruby
114
White

Toan Nguyen viết ngày 07/02/2018

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.)

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Toan Nguyen

3 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
8 6
Chưa xem phần 2? Xem (Link) Trong bài viết này tôi giới thiệu cho các bạn về khái niệm function arity, một cách gọi mĩ miều của số lượng argument ...
Lơi Rệ viết gần 3 năm trước
8 6
White
8 1
Tiếp theo (Link) Mình sẽ hướng dẫn cách test căn bản cho API mình tạo. Thật ra mà nói thì mình phải viết test trước khi làm nhưng mà để tránh việc...
My Mai viết 3 năm trước
8 1
White
4 2
__Chú thích__: Đây là bản dịch tiếng Việt của bài viết gốc của tôi. Nếu bạn muốn xem bản tiếng Anh, xin hãy trỏ tới URL (Link) Lời mở (Link) là ...
Lơi Rệ viết hơn 3 năm trước
4 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
3 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!