Jump to content
  • Advertisement
Sign in to follow this  
methinks

Alternatives to singletons for data manager?

This topic is 2045 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

I want to add a central data manager to my project, so that different classes can share data. It will also act as a map to avoid doubling up on resources.

 

[attachment=15516:layout1.png]

 

I figure that there are 3 ways I can do this:

  • create the manager as a global (EVIL)
  • pass the manager to each class as it is created (UGLY)
  • create the manager as a singleton

Every post I see says to avoid singletons, but is this a situation that breaks the rule, or is there a better solution?

 

Also, the graphic data above is just one type of data; realistically there will be many data types. To avoid having a whole mess of singletons, I would create a central data manager that has a factory class for each type:

 

[attachment=15517:layout2.png]

 

Oh yeah, I'm doing this in C++, but the concepts should apply to most languages

Edited by methinks

Share this post


Link to post
Share on other sites
Advertisement
Singletons are globals, just implemented really badly.


I think the real question is: what does centralized "data management" actually entail? What does it mean for a resource to be managed? Try to have resources know how to manage themselves, e.g. using factory methods and smart pointers, instead of centralized resource managers.

In other words: "managers" are a code smell.

Share this post


Link to post
Share on other sites

Ok, so a more generic version of the question: what is the best way to share the data between different classes that are completely un-related?

Share this post


Link to post
Share on other sites

Ok, so a more generic version of the question: what is the best way to share the data between different classes that are completely un-related?

Factory methods and smart pointers.  Sometimes you cannot get away from it, so managers-as-factories (like a general purpose caching resource loader) need to exist.

 

A singleton is something altogether different.  It says "There can only be one of these.  Ever.  So let it be written."  That is nearly always (but not universally) a bad thing.

 

Don't use singletons.

 

 

 

The preferred way to do things is that when an object needs data you will either pass the data as a parameter when you need it, or set it using a mutator/accessor pair (get/set method) at some time during the object's lifetime.

 

In practice you can get very nearly all the data to objects using this method.  A programmer ought to be able to go for many months without touching anything other than direct parameters to their game objects.  

 

Almost everything should fit this usage pattern.  

 

Sadly, large programs have some objects that need certain bits to be accessible on a read-only basis to other parts of the system.  It is not ideal, but in the real world there are occasionally good reasons to create a very minimal, tiny, global object that points to the canonical versions of certain factory methods.  These may include links to things like the game clock, a handle to the main simulator, or a handle to the resource manager.  These should be used sparingly.  On the rare occasion that these must be used, keep the Law of Demeter in mind.

 

Share this post


Link to post
Share on other sites

I have a "CSingleObjects" class that has those global objects as static members. Their constructor is private but CSingleObjects is declared as friend to them so only that can instantiate them. In another header I have defines like

 

#define gl_printer              CSingleObjects::gl_printer
#define mouse                   CSingleObjects::mouse
#define keys                    CSingleObjects::keys

...so when I need them I can write gl_printer instead of CSingleObjects::gl_printer.

 

Not the best solution but it worked perfectly so far.

Share this post


Link to post
Share on other sites

Personally, I believe there is a widespread misconception that everything must be wrapped in a class when programming in C++. I try to stick to the keep it simple philosophy which is to simply keep things as simple as possible, but not any simpler. Singletons are a completely over-engineered version of a global variable. I avoid the use of singletons by creating a file-scope variable that is accessed via a method. It is a clean and simple solution. As for your dilemma, I recommend this approach in order for you to get a reference to the manager (although I prefer to pass references as parameters to the methods that need them whenever possible).

Share this post


Link to post
Share on other sites

Number 2 isn't ugly; it makes the dependencies between your classes explicit and obvious.
If when constructing a ModelLoader, it requires a DataCache as an argument, then it's clear that this dependency exists, and it's easy to follow the layering of the different parts of your architecture.
On the other hand, if the ModelLoader internally acquires a DataCache via a global/singleton, then this is a magical, hidden dependency. This obfuscates the architecture, makes initialization ordering non-obvious, and makes the code much more brittle (harder to maintain) in the long run, due to all the hidden interactions.

 

Just playing devils advocate here, but couldn't you make the argument that that's a good thing? i.e. I need a to load a model, so I create a ModelLoader; I don't actually care how it internally loads models. And if someone one day decides to change how ModelLoader loads models, I don't want my code to suddenly break.

Share this post


Link to post
Share on other sites

Just playing devils advocate here, but couldn't you make the argument that that's a good thing? i.e. I need a to load a model, so I create a ModelLoader; I don't actually care how it internally loads models. And if someone one day decides to change how ModelLoader loads models, I don't want my code to suddenly break.

I think the point is more that they're both rather ugly but the uglyness of passing dependencies is a major improvement over the problems globals or singletons present for the situation. It's a balance of "more code to write and text to visually process" against "making it clear what a class does without requiring you to look inside it."

Personally I can kinda see where it comes from, I tend to use "manager" classes in my code a lot still just because I find them convenient and easier to follow in many cases, but I pass them around as dependencies. Having tried the singleton approach I can honestly say that it removes the clarity of code in what an object needs access to in order to function. When you have to pass it some kind of rendermanager then you know it is going to draw something, when you pass it an audio manager you know it needs to play sound. That kind of thing.

Using globals causes dependencies that you don't even know are actually there, you may remove some object that is a dependency without even knowing it is one, suddenly your happy and encapsulated little class is breaking and you have to poke around it's innards to find out why. Edited by Satharis

Share this post


Link to post
Share on other sites

It's a balance of "more code to write and text to visually process" against "making it clear what a class does without requiring you to look inside it."

 

Talking about that, sorry to "take over" the thread, but I just wanted to ask a question related to that, and so I don't have to open another thread.

 

I'm actually ending up with a whole lot of (data) managers I need to pass around. That gets really ugly at times, like here:

 

MainMenuState(gfx::Resources<Texture>& textures, gfx::Resources<Material>& materials, gfx::Resources<Mesh>& meshes, gfx::Resources<Effect>& effects, ecs::EntityManager& entityManager, ecs::SystemManager& systemManager, ecs::MessageManager& messageManager);

Especially for my game states, since every game state might create another game state that needs some other of those "managers", I basically have to pass and store them into each one of them. Are there any good strategies to handle this sort of (growing) complexity? Talking generally for situations where I have to pass many references around that are not directly related, but could theoretically be grouped...

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.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!