本節書摘來自華章出版社《effective ruby:改善ruby程式的48條建議》一書中的第2章,第2.10節,作者 [美]彼得 j.瓊斯(peter j. jones),更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視
ruby語言存在兩種用@辨別的變量:執行個體變量和類變量。每個運作中的ruby程式都有自己的私有執行個體變量集,如你所知,它們的名字都以“@”符号開頭。修改對象的執行個體變量不會影響另一個對象的同名執行個體變量。(但是改變變量就是另外一回事兒了;如果兩個對象共享第三個對象的引用,那麼修改自然會影響到另一個了。詳見第16條)。
類變量(那些以“@@”開頭的變量)則被差別對待。不同于綁定在單個上對象,類變量被綁定到一個類型之上,并且它們對于那個類的所有的執行個體都是可見的。設定一個對象的類變量會立即反映到這個類的其他對象上。當然,這也沒什麼值得驚奇的,大多數面向對象程式設計語言都具備這種類範圍内共享變量的能力。不過類變量和繼承體系的互動方式可能是會讓你感到驚訝。
類變量通常的用途是(當然不是唯一的用途)實作單例模式(不要和ruby單例類相混淆)。某些時候在大型的應用中你隻希望某個類存在一個執行個體,并讓所有的代碼去通路這個唯一的執行個體。比如配置類。你可能希望一次性解析配置檔案,然後在整個應用中共享資訊,而不是通過變量在每個需要它的方法中傳來傳去。基本上,單例模式就是用來解決這種神聖的全局變量問題的。
暫且抛開并發性,讓我們編寫一個實作了單例模式的簡單的類,并通過繼承該類實作其他的類:

通過單例類型的定義可以知道,它們不能存在一個以上的執行個體,是以典型的做法是使new方法私有化。這就阻止了任何想從外部建立新對象的可能。我們還要對dup和clone方法做同樣的處理以保證不能對唯一的執行個體進行克隆。通路這個正式對象的唯一方法是通過那個名為instance的類方法。它的職責是確定new方法隻被調用一次,這是通過将其存在名為@@single的類變量中來實作的。如果變量@@single的值為nil,那麼instance方法就會調用new方法建立一個新的執行個體并将其存在@@single類變量中。否則,它隻是傳回類變量@@single目前的值。讓我們建立兩個繼承自類singleton的類,進而使它們可以使用相同的邏輯:
目前看來還不錯。一個使用了這些類的應用可以在其代碼的任何地方通路唯一的配置檔案對象和唯一的資料庫連接配接對象。但是如果這樣就萬事大吉,那我也不會額外地寫這麼多代碼了,讓我們看看真正使用這些類的時候會發生什麼吧:
糟糕!發生什麼了?顯然,兩個類共享了同樣的類變量。當conf?iguration::instance被調用的時候@@single變量的值還是nil,是以調用了new方法并将結果對象進行了緩存。而當database::instance被調用的時候類變量@@single因為已經有值(配置類的對象)了就沒有再調用new方法建立新對象而是直接将其傳回。解決這個問題的方法雖然很簡單,但還是需要解釋一下。
超類中的類變量會被所有子類共享。有點混亂的是,這些類的任何執行個體變量都可以通路并修改這些共享類變量。執行個體方法和類方法共享同樣的類變量,使它們變得像全局變量那樣不可取。是以執行個體應該堅持使用執行個體變量。當然這裡其實是有解決方案的。類也是對象,是以它們也有執行個體變量。每個類都是一個唯一的對象并持有自身的私有執行個體變量集合。将變量@@single從一個類變量改為一個類的執行個體變量就可以解決這個
問題:
如果你之前沒有見過類方法中的執行個體變量,這看上去有些奇怪。同時也很容易把一個對象的執行個體變量與之相混淆。但是它們确實屬于其自身的類。如果你記得類方法其實是一個假象的話就容易了解了。因為類也是對象,我們所稱的“類方法”實際上就是類對象的執行個體方法。類方法僅僅是從名字上來說的。
除了打破了類和子類的共享關系以外,類執行個體變量同時也提供了更多的封裝特性。盡管類變量(@@)可以直接在執行個體或類方法中被通路或修改,類執行個體變量(“@”)卻隻能在類定義級别或者類方法中被通路。是以,類的執行個體或外部的代碼隻有在你為它們特别定義了類方法(比如說singleton::instance)的情況下才能通路。你應該像保護其他任何執行個體變量一般保護它們的通路權限,隻在必要的時候提供通路權限。
順便說一句,之前我說先把并發相關的問題放一放,那是因為類變量以及類的執行個體變量和全局變量一樣有着相同的問題。如果你的程式存在多線程控制,那麼在不使用互斥鎖的情況下改變任何變量都是不安全的。還好,singleton子產品作為ruby标準庫的一部分,正确地以線程安全的方式實作了單例模式。使用它們很簡單:
要點回顧
優先使用執行個體變量而非類變量。
類也是對象,是以它們擁有自己的私有執行個體變量集合。