單件的好處在于它可以在任何地方被任何類使用??梢园阉斫鉃橐粋€全局類(請參照“全局變量”的概念來加深理解)。如果在很多不同的地方需要使用同一組數(shù)據(jù)和方法,那么這時候單件就非常有用。音頻就是一個很好的示例,因為任何類(無論是玩家類、敵方類、菜單類或過場動畫類)都可能需要播放音效或切換背景音樂,所以選擇用單件來播放音頻是明智之舉。與此類似,你可能希望將全局的游戲狀態(tài)信息(可能是玩家持有的兵器數(shù)目,也可能是軍隊的一個排有多少人,等等)存儲在一個單件中,這樣就可以在游戲的不同關(guān)卡中使用同一份數(shù)據(jù)了。
如代碼清單3-1所示,單件的實現(xiàn)相當(dāng)簡潔。這個示例用最少的代碼實現(xiàn)了一個單件類:MyManager。其中,靜態(tài)方法sharedManager提供了對MyManager的單一實例的訪問:如果實例不存在,就為MyManager實例分配內(nèi)存并完成初始化;如果存在,就返回實例。
代碼清單3-1 用單件實現(xiàn)示例類MyManager
static MyManager *sharedManager = nil;
+(MyManager*) sharedManager
{
if (sharedManager == nil)
{
sharedManager = [[MyManager alloc] init];
}
return sharedManager;
}
不過,單件也有它的缺點。正因為它們易于實現(xiàn)、方便使用,而且可以在其他任何類中使用,所以人們常會在一些不應(yīng)該用單件來實現(xiàn)的場合使用它們。
例如,你可能想:我只有一個玩家對象,為什么不把這個玩家類設(shè)為一個單件呢?這種想法似乎很完美。但你會突然發(fā)現(xiàn),只要玩家進入下一個關(guān)卡,他就不僅帶著上一輪的分?jǐn)?shù),而且還保持著上一輪最后的動作、血量、撿到的所有道具。甚至,他剛進入下一關(guān)就已經(jīng)在“狂暴”模式(Berserk Mode)下了,而這恰恰是因為他離開上一關(guān)的時候就處于這個模式。
為了解決這個問題,你會去添加幾個方法,在關(guān)卡發(fā)生變化時對一些變量進行重置。到目前為止,一切都進行得很順利,但是當(dāng)你為游戲設(shè)計更多特性時,你會發(fā)現(xiàn)轉(zhuǎn)換關(guān)卡時需要添加好多變量,而且還要對很多已有變量進行維護。更糟糕的是,假如有一天朋友建議你為iPad版本添加雙人模式,這時候你想起玩家類是個單件!任何時候都只能有一個玩家實例!于是你將面臨一個非常令人頭疼的問題:要么重寫大量代碼進行徹底重構(gòu),要么放棄雙人模式。
越是依賴于單件,就會碰到越多這樣的麻煩事。所以,在你創(chuàng)建一個單件類之前,務(wù)必要考慮清楚,對于這個類以及它的數(shù)據(jù),是否真的只需要一個實例?這個設(shè)計以后是否會發(fā)生變化?