应用场景
以下为 Cocos2D-x 3.0 的示例:Director、TextureCache、SpriteFrameCache、AnimationCache、UserDefault、GLProgramStateCache、ScriptEngineManager、PoolManager、FileUtils、Profiler、SimleAudioEngie、Configuration、Application、GLProgramCache
为什么会有这样的单例存在呢?
Director单例,负责初始化OpenGL渲染窗口和游戏场景的流程控制,是cocos2dx游戏开发中必不可少的类之一。为什么要将这个类设计成单例对象呢?因为一个游戏只需要一个游戏窗口,所以只需要初始化一次OpenGL渲染窗口即可。而场景的流程控制功能也只需要有这样一个场景控制对象存在即可。为了保证Director类的实例对象只有一个,就必须使用单例模式。但是如果我们需要在一个应用程序中嵌套多个Cocos2D-X实例,则Director类不能作为单例使用。
TextureCache单例。该类主要负责加载游戏中需要的纹理图片资源,这些资源加载完成后就可以一直保留在内存中,下次需要使用这张纹理时,只需要返回对应的纹理对象引用即可,无需重复加载。当然这样可能会浪费内存,所以cocos2dx采用了引用计数的方式来管理对象内存。纹理刚加载时,引用计数为1,如果使用这个纹理对象创建了精灵,纹理对象引用就会加1,如果释放了精灵,对应的引用计数就会减1。当纹理引用计数变为0时,纹理占用的内存自然就会被释放。这也是为什么在收到内存警告时会调用TextureCache的removeUnusedTextures方法的原因,这个方法会释放所有引用计数为1的纹理对象。从字面意思上看,Cache就是缓存的意思,它是以一定的内存压力为代价来换取游戏性能的提升,这种缓存技术在游戏开发中随处可见。 注意:IO操作对游戏性能影响很大,应尽量避免!!!
和SpriteFrameCache、AnimationCache以及GLProgramCache类似,它们也是缓存类,分别负责缓存SpriteFrame、Animation以及GLProgram。这么做的原因无非就是为了性能,用空间换取时间。
UserDefault 该类主要用于游戏中保存数据,它会创建一个 XML 文件,并将用户定义的数据以 key-value 的形式存储在这个 XML 文件中。为什么这个类会成为单例类呢?原因很简单,因为像这种操作数据文件或者配置文件的类,在程序运行过程中通常只需要有一个实例就可以了。但是,我们真的需要这个单例吗?在实际开发游戏的过程中,我们通常会有自己的数据持久化方法,这些方法通常都会被设计成单例类。其实,如果数据持久化方法就那么几个,在匿名命名空间中用几个函数就可以解决。
ScriptEngineManager,该类包含一个实现了ScriptEngineProtocl接口的对象引用,可以帮助我们很方便的找到LuaEngine对象。这里单例的作用纯粹是为LuaEngine提供一个全局的访问点,为什么不直接把LuaEngine当成单例对象呢?是不是有些情况下可以创建多个LuaEngine对象呢?如果cocos2d-x还能同时支持其他脚本引擎,那么其他脚本引擎也可以相应的设计成单例类。当然这样做无疑会让引擎出现过多的单例,考虑到单例模式在近几年受到了很多开发者的诟病,已经被列为“反模式”。这里引用CCScriptEngineManager单例类,为其他引擎对象提供一个唯一的全局访问点,这也是一种解决办法,不知道我的猜测对不对?
PoolManager,该类用于管理AutoReleasePool对象栈。因为cocos2d-x采用引用计数方式管理动态内存,所以所有使用引用计数的管理对象都放在一个当前的autoReleasePool中。每次Director的mainLoop更新时,都会调用PoolManager的pop方法,将当前autoReleasePool中所有autoRelease对象的管理状态设置为false,并清空autoReleasePool。清空的时候会调用autoReleasePool中所有对象的release方法,释放内存。为什么该类要设计成单例呢?因为该类需要在多个地方引用,所以为了引用方便,设计成单例。
FileUtils 类。该类是一个工具类。工具类和配置文件类大多数情况下被设计成单例。因为它们不需要有多个实例。同时,它们也可以被实现为一组类方法,这样就可以不用创建对象就可以使用。
Profiler 类负责分析 cocos2d 及其运行的性能,也是一个工具类,所以之所以设计成单例类,和 FileUtils 类类似,这个单例也有点冗余,用简单的名字 + 一些全局函数就可以解决。
Configuration类也被设计成了单例对象。这个类主要负责管理cocos2d-x的一些OpenGL变量信息。这些信息本来可以通过定义一些宏或者全局变量来解决。这里将其设计成单例类cocos2dx游戏开发3D动画,也是一种更加“面向对象”的体现。因为这些配置信息并不需要存在于多个对象中。这个类可以变成Director类的内部私有类,然后通过Director提供一个访问点。
Application 类最初设计用于获取一些平台相关的信息,最重要的是运行游戏的主循环。一个游戏只需要一个应用程序实例,因此它被设计为单例。
SimpleAudioEngine类也被设计成了单例类,因为它给开发者提供了最简单的声音操作接口游戏图片素材,可以很方便的处理游戏中的背景音乐和音效。这个类也应用了外观模式,屏蔽了CocoDenshion子系统中复杂的功能,简化了客户端程序员的调用。为什么这个类被设计成单例呢?因为它到处都是被访问的。设计成单例很方便,而且它和其他对象没有任何联系,不容易使用对象组合。
使用单例模式的优点和缺点
优势:
1)简单易用,限制一个类只能有一个实例,可以降低创建多个对象可能带来的内存问题风险,包括内存泄漏、内存占用问题等。
缺点:
单例模式提供了一个全局的访问点,你可以在程序的任何地方很方便地访问到它,这本身就是一个高耦合的设计,一旦改变了单例,其他模块也需要修改。另外,单例模式把对象变成了全局的,学过面向对象编程的人都知道,全局变量非常邪恶,应该尽量避免。而且,单例模式会导致对象的内存一直存在到程序结束。在一些使用GC的语言中,这其实就是内存泄漏,因为它们永远不会被释放。当然,也可以通过提供一些特殊的方法来释放单例对象所占用的内存,比如上面提到的XXXCache对象,它就有相应的Purge方法。最后,cocos2dx中实现的99%的单例都不是线程安全的。
在讨论利弊的时候,读者肯定已经注意到了,弊端远远大于利端。这提醒大家以后使用单例模式时要谨慎,不要滥用。因为这个模式是最容易被滥用的。只有真正符合单例模式的应用场景时才可以考虑。不要为了方便访问而把任何一个类都做成单例。这样,最后你会发现你的程序里只剩下一堆单例和工厂。
另外,单例模式正在减少,例如 ActionManager 和 TouchDispatcher 在 cocos2d1.0 之前都是单例,但现在都变成了 Director 类的属性。而且 Riq(cocos2d-iphone 的作者)也在邮件中提到,Director 对象将来会变成非单例,一个游戏中可以创建多个游戏窗口。
很多时候,我们考虑实现一个功能的时候,首先想到的一定不是单例。单例就像全局变量一样,会散布在你的程序的各个地方。建议尽量使用类函数,命名空间+全局函数,或者内部类。而且,使用单例其实是程序员心理懒惰的表现,不想去管理内存,因为搞不清楚内存什么时候该释放。反正我只有一个对象,这点内存不算什么,反正以后也不会再钟情于单例了。大部分使用单例的场景其实都可以有替代方案的。
单例模式的定义
确保一个类只有一个实例,并提供一个全局访问点。
代码段:
public class Singleton
{
public:
//全局访问点
static Singleton* SharedSingleton()
{
if(NULL == m_spSingleton)
{
m_spSingleton = new Singleton();
}
return m_spSingleton;
}
private:
static Singleton* m_spSingleton;
Singleton();
Singleton(const Singleton& other);
Singleton& operator=(const Singleton& other);
};
Singleton* Singleton::m_spSingleton = NULL;
注意这只是最基本的实现,没有考虑线程安全或者内存释放,但是这个实现有两个最基本的要素:1.定义一个静态变量,并将构造函数设置为私有。2.提供一个全局的访问点,供外部访问。
如何在游戏开发中运用这个模型?
众所周知,游戏开发离不开游戏数据的保存和加载,这些数据包括关卡数据、游戏状态数据等等。这类信息在很多游戏模块中都需要访问,因此可以为其设置一个单例对象。我武断地认为,客户端游戏开发中至少需要一个单例对象。因为一个全局的访问点可以方便很多对象之间的交互。根据前面的讨论,一些时不时需要的类引用,也可以保存在这个单例对象中,但只需要保存弱引用即可。使用单例最严重的问题就是内存泄漏,所以尽量不要把单例类设计得太复杂,不要让它包含太多的动态内存管理工作。
与其他模式的关系
单例模式一般和工厂模式配合使用,因为工厂类一般都设计成单例对象。
比如上面提到的各种缓存类,在某种意义上也可以看成是工厂对象,由于工厂负责生产对象,所以缓存类可以根据用户的需求,生产出相应的对象。
最后我们来看看cocos2d-x创始人王哲对于单例的看法:“这么说吧,我把单例设计成基本上是一种抽象的情况:独占资源。比如某个硬件IO(比如TouchDispatcher、Accelerometer),比如某个公共的缓存区域(TextureCache、UserDefault)。后来有人吐槽单例类太多,销毁整个cocos2d实例再重建很麻烦,于是小明和riq就把大量的单例类放到了Director中进行管理。”
那么什么时候应该使用 Singleton,什么时候应该避免使用 Singleton?
考虑用例
这个类必须只有一个实例,并且只能通过全局访问点供客户端访问。例如,日志类。但是,日志类可以使用面向方面编程来解决,这种解决方案比单例更优雅。
不应使用此方法的场景。
单例在几乎所有场景下都不应该成为首选,尽管一开始使用单例会带来很多好处。然而随着时间的推移cocos2dx游戏开发,它的各种弊端开始显现。此外,单例在多线程环境中也是一个陷阱。很少有人能正确编写一个线程安全的单例类。
文章来源:https://blog.csdn.net/u012896140/article/details/48051625