单例模式--慎用

单例模式

概念:确保一个类只有一个实例,并提供对它的全局访问点

  • 将一个类限制为只有一个实例:

    常见的情况是当类与维护自己全局状态的外部系统交互时需要这么做,就比如游戏中的用户数据,要是创建多份也就是多个实例,那么我们该用哪一份数据呢?

  • 提供全局的访问点:

    游戏中几个不同的系统将使用“日志记录”,“内容加载”,“游戏状态保存”等时,这些系统可以直接得到对应的实例,使用起来很方便。

class FileSystem
{
public:
  static FileSystem& instance()
  {
    // Lazy initialize.
    if (instance_ == NULL) instance_ = new FileSystem();
    return *instance_;
  }

private:
  FileSystem() {}

  static FileSystem* instance_;
};

 

好处:

  • 如果没有人使用它,它不会创建实例。单例仅在第一次访问时被初始化,如果从来没使用过,它根本不会被实例化。这样也算节省内存了。
  • 它在运行时初始化。这是相对于静态类来说的,静态类在main()调用之前初始化静态成员,也就意味着他们不能使用只有在程序启动并运行后才知道的信息。
  • 单例模式下的类是可以作为基类的。如下代码:
class FileSystem
{
public:
  static FileSystem& instance();

  virtual ~FileSystem() {}
  virtual char* readFile(char* path) = 0;
  virtual void  writeFile(char* path, char* contents) = 0;

protected:
  FileSystem() {}
};
class PS3FileSystem : public FileSystem
{
public:
  virtual char* readFile(char* path)
  { 
    // Use Sony file IO API...
  }

  virtual void writeFile(char* path, char* contents)
  {
    // Use sony file IO API...
  }
};

class WiiFileSystem : public FileSystem
{
public:
  virtual char* readFile(char* path)
  { 
    // Use Nintendo file IO API...
  }

  virtual void writeFile(char* path, char* contents)
  {
    // Use Nintendo file IO API...
  }
};
FileSystem& FileSystem::instance()
{
  #if PLATFORM == PLAYSTATION3
    static FileSystem *instance = new PS3FileSystem();
  #elif PLATFORM == WII
    static FileSystem *instance = new WiiFileSystem();
  #endif

  return *instance;
}

    通过一个简单的开关,实现了文件系统的跨平台。

 

坏处:

这是一个全局变量,对于全局变量来说,虽然访问起来方便了,但是存在以下问题:

  • 它们使推理代码变得更加困难
  • 它们容易产生耦合
  • 它们对于并发并不友好,因为每个进程都可以操作这个变量

即使在实际过程中我们只需要“只创建一个实例”或者“提供全局访问”中的一个就够了,单例模式仍然会两个都满足,这也会带来一些问题:

  有时候会存在某个类只需要保证一个实例,但我不希望每个人都可以去操作它;这个时候可以这么做

 

class FileSystem
{
public:
  FileSystem()
  {
    assert(!instantiated_);
    instantiated_ = true;
  }

  ~FileSystem() { instantiated_ = false; }

private:
  static bool instantiated_;
};

bool FileSystem::instantiated_ = false;

 

有时候只是需要提供对实例的便捷访问,并不限制实例的个数,这个时候可以这么处理:

  • 通过参数将实例传递进去
  • 从基类获取它
    class GameObject
    {
    protected:
      Log& getLog() { return log_; }
    
    private:
      static Log& log_;
    };
    
    class Enemy : public GameObject
    {
      void doSomething()
      {
        getLog().write("I can log!");
      }
    };

     

  • 从已经是全局的对象中获取它
    class Game
    {
    public:
      static Game& instance() { return instance_; }
    
      // Functions to set log_, et. al. ...
    
      Log&         getLog()         { return *log_; }
      FileSystem&  getFileSystem()  { return *fileSystem_; }
      AudioPlayer& getAudioPlayer() { return *audioPlayer_; }
    
    private:
      static Game instance_;
    
      Log         *log_;
      FileSystem  *fileSystem_;
      AudioPlayer *audioPlayer_;
    };

     

 

posted @ 2021-09-07 17:11  悲欢离合的圆缺  阅读(68)  评论(0编辑  收藏  举报