類變數和類方法
一直以來,我們建立的所有類都包含有執行個體變數和執行個體方法:與某個具體的類執行個體相關聯的變數和方法。
有時候,類也需要有自己的狀態。於是就有了類變數。
類變數
類變數在類的所有對象中共用,也能被我們後面介紹的類方法訪問。一個具體的類變數在給定的類中只有一個拷貝。
類變數名由兩個"at"符號開始,如@@count。與全域變數和執行個體變數不同,類變數在使用前必須先初始化。
通常,初始化只是在類內定義一個簡單的賦值。
例如,我們的自動唱機想記錄每首歌的播放次數。當播放歌曲的時候,在執行個體中的這個值會增加。
但我們還想知道總共有多少歌已經播放了。我們可以通過尋找所有的Song執行個體並把它們的計數相加或
冒著脫離良好設計的風險使用一個全域變數來做到。相反,我們使用一個類變數。
Code
class Song
@@plays = 0
def initialize(name, artist, duration)
@name = name
@artist = artist
@duration = duration
@plays = 0
end
def play
@plays += 1 # same as @plays = @plays + 1
@@plays += 1
"This song: #@plays plays. Total #@@plays plays."
end
end
為了調試的需要,我特意在Song#play時返回一個包含了這首歌播放次數的字串和所有歌的播放總次數。
我們簡單地測試一下。
Code
s1 = Song.new("Song1", "Artist1", 234) # test songs..
s2 = Song.new("Song2", "Artist2", 345)
s1.play -> "This song: 1 plays. Total 1 plays."
s2.play -> "This song: 1 plays. Total 2 plays."
s1.play -> "This song: 2 plays. Total 3 plays."
s1.play -> "This song: 3 plays. Total 4 plays."
類變數對於類和它的執行個體是私人的。如果你想使它們能被外面訪問,你將需要編寫一個訪問器方法。
這個方法可以是一個執行個體方法,也可以是下面我們將要提到的類方法。
類方法
有時候類需要提供一些不與特定執行個體綁定的方法。我們已經使用過一個這樣的方法了。
這個new方法建立一個新的Song對象,但它不與具體的song相關聯。
song = Song.new(.)
你Ruby類庫中你都能找到類方法的影子。例如,File類的對象對應一個檔案系統下開啟的檔案。
然而,File類還提供了許多類對象來操作沒有開啟的檔案,所以沒有File對象。
如你想刪除一個檔案,你調用類檔案File.delete,傳入檔案名稱。
File.delete("doomed.txt")
類檔案通過定義來與執行個體方法區分;類方法的名字定義為在方法名字前加上類名和點。
Code
class Example
def instance_method # instance method
end
def Example.class_method # class method
end
end
自動唱機是按歌來計費而不是按時間。這使用短的歌曲比較長的歌曲利潤高。我們想防止在播放清單中的歌曲太長。
我們可以在播放清單中定義一個類方法來檢查一個具體的歌曲是否超出範圍。我們通過一個類常量來設定這個範圍,
這是一個簡單的常量(還記得常量嗎?它們用大寫字母開頭)它在類內初始化。
Code
class SongList
MAX_TIME = 5*60 # 5 minutes
def SongList.is_too_long(song)
return song.duration > MAX_TIME
end
end
song1 = Song.new("Bicylops", "Fleck", 260)
SongList.is_too_long(song1) -> false
song2 = Song.new("The Calling", "Santana", 468)
SongList.is_too_long(song2) -> true
單例模式和其它結構
有時候你想重寫Ruby建立對象的預設。例如,我們的自動唱機。因為我們有許多自動唱機分布在整個國家,
我們想要儘可能簡單地維護它們。其中一個要求是記錄自動唱機發生的所有事情:歌曲播放,收到付款,
奇怪的資料流等等。因為我們想等候音樂的網路頻寬,所以我們會把這些記錄檔案儲存在本地。
也就是說,我們需要一個類來處理這些記錄。然而,我們想要每個自動唱機只有一個記錄對象,
我們還希望這個記錄對象能被其它對象使用。這就是單例模式。我們整理了這些事情,因此建立記錄對象的唯一方式
是調用MyLogger.create,我們將確保永遠只有一個記錄對象被建立。
Code
class MyLogger
private_class_method :new
@@logger = nil
def MyLogger.create
@@logger = new unless @@logger
@@logger
end
end
通過使MyLogger的new方法變為私人,我們阻止並使用這個的建構函式建立記錄對象。
取代的是,我們提供了一個類方法MyLogger.create。這個方法使用類變數@@logger儲存
logger單一執行個體的引用,每次調用這個方法時都返回這個執行個體。
我們通過查看這個方法返回的標識符來測試它。
Code
MyLogger.create.id -> 936550
MyLogger.create.id -> 936550
使用類方法作為偽建構函式能使你的類的使用者更加容易使用。例如,一個描述多邊形的Shape類。
Shape的執行個體使用給定的建構函式建立,它需要邊數和周長兩個參數。
Code
class Shape
def initialize(num_sides, perimeter)
#
end
end
然而,幾年過去後,這個類被用在了不同的應用程式中,這個程式通過名字和一邊的長度來建立圖形,而不是周長。
簡單地給Shape添加一些類方法。
Code
class Shape
def Shape.triangle(side_length)
Shape.new(3, side_length*3)
end
def Shape.square(side_length)
Shape.new(4, side_length*4)
end
end
類方法有許多有趣的強大的應用,但發掘他們並不會讓我們的自動唱機更早地完成,所以讓我們繼續吧。