類別實例變數

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),其後代會繼承這個變數。與類別變數不同,這些類別實例變數會為每個類別物件採用不同的值。

類別實例變數相當罕見,但當您需要它們時,它們很有用。