单件的好处在于它可以在任何地方被任何类使用。可以把它理解为一个全局类(请参照“全局变量”的概念来加深理解)。如果在很多不同的地方需要使用同一组数据和方法,那么这时候单件就非常有用。音频就是一个很好的示例,因为任何类(无论是玩家类、敌方类、菜单类或过场动画类)都可能需要播放音效或切换背景音乐,所以选择用单件来播放音频是明智之举。与此类似,你可能希望将全局的游戏状态信息(可能是玩家持有的兵器数目,也可能是军队的一个排有多少人,等等)存储在一个单件中,这样就可以在游戏的不同关卡中使用同一份数据了。
如代码清单3-1所示,单件的实现相当简洁。这个示例用最少的代码实现了一个单件类:MyManager。其中,静态方法sharedManager提供了对MyManager的单一实例的访问:如果实例不存在,就为MyManager实例分配内存并完成初始化;如果存在,就返回实例。
代码清单3-1 用单件实现示例类MyManager
static MyManager *sharedManager = nil;
+(MyManager*) sharedManager
{
if (sharedManager == nil)
{
sharedManager = [[MyManager alloc] init];
}
return sharedManager;
}
不过,单件也有它的缺点。正因为它们易于实现、方便使用,而且可以在其他任何类中使用,所以人们常会在一些不应该用单件来实现的场合使用它们。
例如,你可能想:我只有一个玩家对象,为什么不把这个玩家类设为一个单件呢?这种想法似乎很完美。但你会突然发现,只要玩家进入下一个关卡,他就不仅带着上一轮的分数,而且还保持着上一轮最后的动作、血量、捡到的所有道具。甚至,他刚进入下一关就已经在“狂暴”模式(Berserk Mode)下了,而这恰恰是因为他离开上一关的时候就处于这个模式。
为了解决这个问题,你会去添加几个方法,在关卡发生变化时对一些变量进行重置。到目前为止,一切都进行得很顺利,但是当你为游戏设计更多特性时,你会发现转换关卡时需要添加好多变量,而且还要对很多已有变量进行维护。更糟糕的是,假如有一天朋友建议你为iPad版本添加双人模式,这时候你想起玩家类是个单件!任何时候都只能有一个玩家实例!于是你将面临一个非常令人头疼的问题:要么重写大量代码进行彻底重构,要么放弃双人模式。
越是依赖于单件,就会碰到越多这样的麻烦事。所以,在你创建一个单件类之前,务必要考虑清楚,对于这个类以及它的数据,是否真的只需要一个实例?这个设计以后是否会发生变化?