Alternatives to singletons for data manager?

Started by
30 comments, last by _the_phantom_ 10 years, 11 months ago

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

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.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

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?

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.

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.

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).

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


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.

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.

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

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.

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...

This topic is closed to new replies.

Advertisement