類別實例變數
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),其後代會繼承這個變數。與類別變數不同,這些類別實例變數會為每個類別物件採用不同的值。
類別實例變數相當罕見,但當您需要它們時,它們很有用。