类实例变量

2007年1月9日

当你学习对象时,通常会学习到它们可以捕获两种类型的数据:实例和类。实例变量是最常见的情况,数据随对象的每个实例而变化。类变量,通常称为静态变量,在类的所有实例之间共享。每个实例都指向相同的值,并且所有实例都可以看到任何更改。类变量比实例变量少得多,尤其是可变类变量。

类变量与继承的交互方式是一个特殊的问题。考虑一个用于存储自身实例的类变量。(如果对 ruby 不熟悉,请参阅我的 阅读指南。)

#ruby
class Employee
  @@instances = []
  def self.instances
    return @@instances
  end
  def store
    @@instances << self
  end
  def initialize name
    @name = name
  end
end

Employee.new('Martin').store
Employee.new('Roy').store
Employee.new('Erik').store

puts Employee.instances.size

这里没有意外,有三个员工。但现在试试这个。

#ruby
class Employee
  @@instances = []
  def self.instances
    @@instances
  end
  def store
    @@instances << self
  end
  def initialize name
    @name = name
  end
end

class Programmer < Employee; end
class Overhead < Employee; end

Overhead.new('Martin').store
Overhead.new('Roy').store
Programmer.new('Erik').store

puts Overhead.instances.size
puts Programmer.instances.size

这里的输出是 3 和 3,而我们可能更希望是 2 和 1。原因是类变量在类的所有实例之间共享,包括所有子类。有两个类,但只有一个变量。

有时,这种跨层次结构的变量正是我们想要的,但有时,就像在这种情况下,我们更希望每个类都有一个不同的变量。我第一次在 Smalltalk 的一些后期版本中遇到了这个概念,它被称为类实例变量。你可以像使用类变量一样引用类实例变量,但每个类都会得到不同的值。

OO 语言中对类实例变量的支持并不常见,但自己实现它并不难。最明显的方法是使用以类名为键的字典。

#ruby
class Employee
  @@instances = {}
  def self.instances
    @@instances[self]
  end
  def store
    @@instances[self.class] ||= []
    @@instances[self.class] << self
  end
  def initialize name
    @name = name
  end
end

class Overhead < Employee; end
class Programmer < Employee; end

Overhead.new('Martin').store
Overhead.new('Roy').store
Programmer.new('Erik').store
puts Overhead.instances.size
puts Programmer.instances.size

你可以在任何 OO 语言中使用这种技术。然而,Ruby 实际上有类实例变量。

#ruby
class Employee
  class << self; attr_accessor :instances; end
  def store
    self.class.instances ||= []
    self.class.instances << self
  end
  def initialize name
    @name = name
  end
end

class Overhead < Employee; end
class Programmer < Employee; end

Overhead.new('Martin').store
Overhead.new('Roy').store
Programmer.new('Erik').store
puts Overhead.instances.size
puts Programmer.instances.size

类实例变量的定义是片段 class << self; attr_accessor :instances; end。出于我不想深入探讨的原因,这在类 employee 上定义了一个实例变量(以及 getter 和 setter),该变量被其后代继承。与类变量不同,这些类实例变量对于每个类对象将采用不同的值。

类实例变量非常罕见,但在你需要它们的时候很有用。