Jump to content
  • Advertisement
Sign in to follow this  
kitman20022002

How to avoid Singletons/global variables

This topic is 795 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Heard it it bad to use but not much example tell you how to replace/avoid to it , considering the following case 

//making things public so it's just faster for us to understand the example

//Texture.h
class Texture
{
 public:
   Texture(string a_name){
         m_name = a_name;
         m_ID = TextureManager.GenTexture(a_name);
   }
   string m_name;
   int m_ID;
};

//Texture Manager.h
class TextureManager
{
 public:

 //this store the name of the texture and the ID of the texture
 static Map<string, int> m_textures;
 static int GenTexture(string a_name){

    //assume the following code works, you guys get the meaning  
    for(int i =0 ; i<m_textures ; i++)
    {
       if(m_textures[i].name == a_name)
       {
         return m_textures[i].ID; 
       }
    }
    int id = OPENGL_genTexture(a_name);
    m_textures.Add(a_name , id);
    return id; 
  }

 
}

There's only know one way to not use slot of not use global variable , but it does not make sense to me 

//Texture.h
class Texture
{
 public:
   Texture(string a_name){
         m_name = a_name;
         m_ID = TextureManager.GenTexture(a_name);
   }
   string m_name;
   int m_ID;
private:
   static Map<string, int> m_textures;
   static int GenTexture(string a_name){
    for(int i =0 ; i<m_textures ; i++)
    {
       if(m_textures[i].name == a_name)
       {
         return m_textures[i].ID; 
       }
    }
    int id = OPENGL_genTexture(a_name);
    m_textures.Add(a_name , id);
   return id ;
  }

};


so it there anyone can give me recommendation what to do about it?  thanks  

Edited by kitman20022002

Share this post


Link to post
Share on other sites
Advertisement
In the original example I would just have TextureManager create the Textures via a static factory function, allocating the IDs as appropriate, and it can pass in the ID to the Texture's constructor.

Share this post


Link to post
Share on other sites

The question is: is it global state?  This is a design decision you need to make on a case-by-case basis, and it requires having an understanding of what you are actually interfacing with in order to make the decision.

 

Take textures in OpenGL since that's the example you used.

 

Textures aren't global state; a texture manager isn't global state and the current texture binding isn't global state.  Texture objects are per-context state, may or may not be shared by multiple contexts depending on whether the appropriate platform-specific call (e.g wglShareLists) was made, only last as long as the context lasts, and there may be more than one context.

 

As you can see you're already in a position where you thought you'd only need one texture manager but instead you may actually need more than one.  I find that's a good general principle to keep in mind everytime you think "I know, I'll use a singleton": if you have one of something there's a good chance that having more than one is, if not required, at least possible.  So begin by designing around having more than one and you save yourself a bunch of future pain.

 

So it's no longer TextureManager->GenTexture; it's Context->TextureManager->GenTexture and because you may have more than one context, Context shouldn't be a singleton either.

 

On the other hand, if it is legitimately global state then by all means make it a global or singleton.  This is actually a much cleaner design than having multiple copies of it all of which need to be kept in sync.  Form should follow function.

Share this post


Link to post
Share on other sites

As others have mentioned the real major issue with global data is mutation, who can change it and when.

 

It makes code very brittle and also reuse of any code that accesses becomes a pain as you have to use the same global variables/state project to project.

 

Injection and other similar techniques solve the reuse issue, prefer objects are supplied the data they require than go off and get it from some magic location.

 

Injection does not solve the mutation issue though. How you solve data changing under your feet is case by case. It might be the data is initialised and then read only or you have locking etc.

Edited by WozNZ

Share this post


Link to post
Share on other sites

You shouldn't avoid using Singletons, as they have times when their use case comes in. The issue is that people tend to use them for the absolute worst reasons in the world. I personally do not understand why people keep telling you to avoid them, when it's use case is obviously for data that needs to be globally accessed, but is NOT expected to mutate much at runtime. You could try abstracting the mess out of code to avoid this for a "better" pattern and a level of unneeded complexity. Trust me, you can save time.

You're welcome to use a singleton for each manager. However, I suggest using the Singleton as a container to hold references to -ALL- of your resource managers. You will most likely not use more than one of each resource manager. You expect all of them to exist. It does expose these managers to the rest of the code, but honestly unless a programmer is being stupid, there shouldn't be a reason that's a problem.

Edited by Tangletail

Share this post


Link to post
Share on other sites

I personally do not understand why people keep telling you to avoid them, when it's use case is obviously for data that needs to be globally accessed, but is NOT expected to mutate much at runtime.

 
In the short term it seems to work as a shortcut. Everything can use this one magical global object.
 
When your project is still tiny you can keep track of those little dependencies.

 

But every time you use them, you introduce a hidden dependency. Every time you modify them, you introduce a hidden dependency.

 

As the project grows you gain more and more hidden dependencies.  As more team members are added people trip over the hidden dependencies.  As time passes you forget what dependencies exist elsewhere in the code, and subtle bugs are introduced. 

 

Soon these hidden dependencies are snares covering your entire code base.  Changes to one thing introduce breaking changes to other areas of your code. It is difficult or impossible to write code tests because the hidden dependencies are global objects, you cannot swap them out with temporaries or mock objects or fake objects or test double objects.  You cannot extend the underlying systems because they are concrete instances rather than interfaces. 

 

 

If your projects always remain small and never grow to an appreciable size then global variables do indeed make your typing and your designs slightly easier.  But if that system ever grows you will need to rip them out and replace them with a better system, or your development environment will be full of landmines.  And typically those landmines range from tricky to nightmarish when it comes to bugs.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!