“确保一个类只有一个实例,并为其提供一个全局访问入口。”(摘自《游戏编程模式》)
- 单例的设计对象本身就不应该拥有多个实例,或如果拥有就不能正常运行。
- 适合使用单例模式的例子有:文件封装类(同一个文件的读与写操作不能同时发生)、日志记录、文件加载、游戏状态存储等
- 单例模式提供了一个单一的实例、提供了全局获取位移实例的方法,这是该模式的重要特性。
- 单例模式是一个双刃剑,我们使用时要保持清醒的头脑,他或许在短期内具有比较好的收益,但我们也应考虑如何避免使用单例模式。
提供全局指针以访问唯一实例
- 实现代码:
class FileSystem { public: static FileSystem& instance()//获取唯一实例的全局方法 { if(m_Instance==NULL) m_Instance=new FileSystem(); return m_FileSystem; } private: FileSystem() {} static FileSystem* m_Instance; };
- 更现代的版本
class FileSystem { public: static FileSystem& instance() { static FileSystem* instance =new FileSystem(); return *instance; } private: FileSystem() {} };
- 上述运用单例模式的优良特性:
- 如果没有使用这个类,那么就不会创建实例。
- 它在运行时初始化
- 它可以继承单例。这是一个强大但是经常被忽略的特性。
- 可继承的代码例子:
class FileSystem { public: static FileSystem& instance(); virtual ~FileSystem() {} virtual char* read(char* path) = 0; virtual void write(char* path,char* text) = 0; protected: FileSystem() {} }; //继承来为不同平台定义派生类 class PS3FileSystem : public FileSystem { public: virtual char* read(char* path) { //Use Sony File IO API } virtual void write(char* path,char* text) { //Use Sony File IO API } } class WiiFileSystem : public FileSystem { public: virtual char* read(char* path) { //Use Nintendo File IO API } virtual void write(char* path,char* text) { //Use Nintendo File IO API } } FileSystem& FileSystem::instance() { #if PLATFORM == PLAYSTATION3 static FileSystem* instance=new PS3FileSystem(); #elif PLATFRORM == WII static FileSystem* instance=new WiiFileSystem(); #endif }
单例模式不好的方面
- 它是一个全局变量,而全局变量是有害的。
- 全局变量令代码变得生涩。
- 全局变量促进了耦合
- 全局变量对并发不友好。
怎么处理单例模式
- 是否需要类:在设计类时,并不是所有都适合新创建一个类,而是将其包含至同一类便可。过多创建类或许是贪图思考的遍历,这样的代码结构或许太松散,例子如下:
class Bullet { public: int getX() const {return X_;} int getY() const {return Y_;} void setX(int x) {X_=x;} void setX(int y) {Y_=y;} private: int X_; int Y_; } //管理它生成的类-------思考一下是否有必要创建 class BulletManager { public: Bullet* create(int x,int y) { Bullet* bullet=new Bullet(); bullet->setX(x); bullet->setY(y); return bullet; } bool isOnScreen(Bullet& bullet) { return bullet->getX()>=0&& bullet->getX()<=WIDTH&& bullet->getY()>=0&& bullet->getX()<=HEIGHT; } //more Operate } //我们在处理上可以整合成一个类,将上面所需方法完全整合成一个类Bullet 代码再给出,详细代码请阅读原书。
- 将类限制为单一实例:用于不需要全局访问的情况。当我们只需要再某一段代码访问时,只需要在类中设置一个bool的标志,用来确定是否已经初始化生成过实例。代码如下:
class FileSystem { public: FileSystem() { assert(!instantiated_); instantiated_=true; } ~FileSystem() { instantiated_=false; } private bool instantiated_; } bool FileSystem::instantiated_=false;
- 生成一个实例后,tag变量instantiated_为true,这时不允许实例化,保证仅仅存在该类的一份实例。当析构时,唯一实例被释放,tag变量instantiated_重新回到false,这时类可再次实例化。
- 为实例提供一个便捷的访问方式
- 便利的访问是我们使用单例的主要原因,他让我们随时随地获取所需对象。
- 当使用单例时,通用的原则是:在保证功能的情况下将变量限制在一个狭小的范围内。
总结
- 单例模式便于我们去访问一个对象
- 单例模式使用场景为:在全局或某一个区域只应该存在一个实例的时候
- 单例模式要慎用!!!可以使用其他手段完成单例模式的思想
知识点、图片全部源于《Game Programming Patterns》,中文名《游戏编程模式》