单例模式(singleton)的意思就是只有一个实例。
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
一些资源管理器常常设计成单例模式。
在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler, 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
cocos2d充分使用了单例设计模式,这里有必要提及它是因为它是经常是讨论热点。大体上说,一个singleton是一个类,它在应用使用期中仅仅实例化一次。为了确保如此,就要使用一个既创建又访问此对象实例的静态方法。因此,你不使用alloc/init或者静态自动释放初始化器(static autorelease initializer),而是使用以shared开通的方法来访问一个单例对象。这里有一些cocos2d最常用的单例方法,并告诉你怎么访问:
CCActionManager* sharedManager = [CCActionManager sharedManager];
CCDirector* sharedDirector = [CCDirector sharedDirector];
CCSpriteFrameCache* sharedCache = [CCSpriteFrameCache sharedSpriteFrameCache];
CCTextureCache* sharedTexCache = [CCTextureCache sharedTextureCache];
CCTouchDispatcher* sharedDispatcher = [CCTouchDispatcher sharedDispatcher];
CDAudioManager* sharedManager = [CDAudioManager sharedManager];
SimpleAudioEngine* sharedEngine = [SimpleAudioEngine sharedEngine];
上面的单例你可以任何时候在任何类的任何地方使用。这有点儿想全局类,就像全局变量。如果你有一组用在很多地方的数据和方法,单例就非常有用。音频是一个好例子,因为你任何类(无论是主角、敌人、菜单按钮、场景剪辑)都需要有呻吟效果,或者更换背景音乐。因此使用一个单例来播放声音就很有道理。类似的,如果你有全局游戏状态,可能是玩家军队的数量,或者每组军队的人数,你就需要用一个单例来存储这些信息,因此,你可以带着它从一关到另一关。实现一个单例非常直接,就像下面的代码所示,这段代码用最少篇幅通过单例方式实现了MyManager类。静态方法sharedManager授权访问单个MyManager实例。如果这个实例不存在,就会给以个MyManger实例分配内存并初始化,否则,就返回这个已存在的实例。
static MyManager *sharedManager = nil;
+(MyManager*) sharedManager {
if (sharedManager == nil) {
sharedManager = [[MyManager alloc] init];
}
return sharedManager;
}
不过单例模式也有差劲一面。因为他们易于使用和实现,并能从任何其他类访问,那旧友在不该使用的地方出现的可能。
例如,你可能认为你只有一个player对象,那么为什么不把他作为单例呢?皆大欢喜,但是,你会意识到每当player从一关升级到另一关的时候,这个单例不仅保留着原有的分数,而且保留了他的最近动画状态、生命值以及一切带着的项目,甚至它会以狂暴模式开始新一关,只因为他离开上一关的时候这个模式是活动的。
为了修正此误,你就要添加一个方法在进另一关的时候去重置某些变量。到目前还好,但是当你在游戏中添加更多特性时,你就会停下来不得不添加和维护越来越多的变量。更糟糕的是,设想某天一个朋友建议你给iPad来一个双人模式的版本。但是,等等,你的player是单例,你在任何时候都只能是唯一的!这让你很头大:重构大量代码还是错失双人模式的大好机会?
你越依赖单例模式,你面临的问题就越多。在常见一个单例类前,一定要考虑好是否你真的需要这个类的唯一实例,是否后面会有修改。