Alternative to singleton

Started by
64 comments, last by ApochPiQ 12 years ago
After reading this thread, I started considering the singletons I use in my own project (my own DX10 renderer)

I currently have a few manager classes, (mainly for loading, storing and distributing resources) implemented using the Curiously Recurring Template pattern. This was done to stop me having to pass them in as a reference to everything that might need a resource; if anything needs a resource, it can just ask for one by calling a static function which then calls the non-static one in the instance and the manager takes care of creation, reference counting and destruction.

This struck me as nicely elegant and stripped out lots of cases where I was passing 2 or 3 references in, every time I created something like a game entity (MeshManager, TextureManager, InputManager etc), but reading up I've been seeing that this might not be as good an idea as I thought.

Why is reference passing 'better', and is there a better way of doing it than just putting references in the constructor arguments of the objects that need to use them? Or is it ok to continue as I am?
Advertisement
I dont think either is particularly "better". It is what works best for you and your architecture. Singletons just have an innate design constraint of "there can be only one!" which can cause major refactoring (among headaches) if there ever has to be two or more.

Why is reference passing 'better'


It's easier to use in a threadsafe manner. It's easier to unit test. It's a more flexible design should you need different managers in the future.


is there a better way of doing it than just putting references in the constructor arguments of the objects that need to use them?


That depends on your need. Even if you want a global default that doesn't need to be a singleton.
First, let's take a look at what a Singleton really is. It's a global object with a rule that there can only be one in existence. It denotes that if two of them ever existed at some point, it would be fatal error in your program, and so that must be stopped. Now look at your program. Should having two resource loaders of the same type be a fatal error in the program? Should having two "mesh managers" (or whatever your manager classes are, seeing as a manager class is often a vague and abused thing) trigger a fatal error in the program? No. And if it does, you better have a friggin' good reason. So what part of the Singleton are you really using? The global part. Which implies you really just want a global variable, not a Singleton.

So now the question is "should I use global objects in my game?" Usually, the solution to avoiding global objects is proper program design. A lot of times people design classes that dip their fingers into other classes, when really that's none of their business. If you can properly design each component of your project, there will be little coupling and less need of reference passing. And when you really do need a reference, just pass it! It's not meant to be more complex; rather, it's meant to be more simple. It is harder, however, to design a clean system like this. That's the trick: the program design. Globals are usually just a quick hack to solve a problem, when the real problem is the design of the program.

And I think Telastyn hit some really big things with the "why reference passing is better." In addition, you get to avoid the "Static Initialization Order Fiasco" when you don't use statics/globals, which seems to happen as projects use more and more statics/globals.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Would you ever write a non-trivial app that hard-codes file references: void parseRequest() {
string filename = "c:\data\foo\bar.dat''?
// do XML/JSON/binary/etc parsing here
}


How about string comparison function which hard-codes strings:bool areEqualFooAndBaz() {
string a = "foo";
string b = "baz".

if (a.length() != b.length()) return false;
for (int i = 0; i < a.length(); i++) if (a != b) return false;
return true;
}
See how easy it is? One no longer needs to pass all those annoying parameters into functions, the functions just know what to do. It makes calling functions so much easier and code is so much simpler.


Well, singletons do just that. They hard-code implementation and values into code. Nobody would think of above as good design, yet hard-coding singleton references does just that.


Finally, singleton/global by itself is far from bad, it's cornerstone of many applications (filesystem/file/database/URL are all conceptual singletons - there is only one www.example.com). But as with above examples:string a = "foo"; // singleton, compiler makes it such
string b = "baz"; // singleton, compiler make it such

if (strcmp(a, b) == 0) ... // code references data/values, it does not inhibit them, so a and b may be anything, including a singleton
Antheus, that's a fantastic explanation, thank you :)
I'm similarly keen on hearing alternatives. I know some people just pass references to everything they might need everywhere, but that seems deficient (especially when you need to update 5-10 interfaces every time you add a new function parameter - then roll back those changes should you change your mind..). Do people pass around context objects, and fill the context with things they might use? That way if you really feel the code has no business accessing something, you can block it from accessing that data from the context?

Say you have a load function that takes XML data nodes as input. Then you want to add template support, where XML nodes can reference a (shared) template by name. Access to the template manager/directory can be added in a few lines with a singleton, and for all intents and purposes it is a global table of which there should only ever be one, ever (really). The alternative is to update possibly thousands of lines of code to accept a template manager. This strikes me as a horrendous maintenance burden (I mean, what if you decide you don't want to use templates any more!!). If you had a load context object, you could simply add the template manager to the load context, case closed. But I can't help but feel purists will see a load context object as a thinly veiled singleton? How would people tackle this kind of problem? In fact, as I think about this, if you have a third party library, you really don't want to force your clients to support templates, so it would seem passing in a template manager explicitly is not an option, as the interface shouldn't force the client to pass in what is optional data.

The other issue I've seen is where your graphics engine often needs access to things that aren't readily available in the absence of singletons. Say you have an effect that suddenly needs access to the sound engine for spectrum analysis (so a water shader can respond to sound waves or something) - so you add your graphics sound wave interface/ representation, but how do you acquire the source wave data, if you don't have a globally available lookup table. Do you pass in a sound interface manager/directory to all graphics engine components, which may or may not need to access that data? What if you extrapolate this case to temperature properties, to emulate heat distortion? You start creating a situation where you're passing all this data the graphics engine might need, which just doesn't feel right to me. Being forced to pass in parameters that might never be touched strikes me as a design flaw - so what are people doing out there?


It's easier to use in a threadsafe manner. It's easier to unit test. It's a more flexible design should you need different managers in the future.


Did you have a specific example for how it makes unit testing easier? At a unit test level, I see no fundamental difference between instancing an object and passing to to the unit test methods, versus destroying and recreating the singleton before calling the unit test methods.

There's an ongoing maintenance penalty with passing everything around, shouldn't it come down to whether you can reasonably expect you will ever need that design in future? What are the chances you are going to need two sound devices? Vanishingly small, and should that requirement come about, I think it's going to be demonstrably more efficient to go in and turn it into a function parameter after the fact (truly, it's not that hard), over maintaining code for decades that passes the sound engine to all functions that might need to play some audio.

Some module doesn't play sound any more? Purists will then update 5 or 10 function signatures to 'clean' things up, since the sound engine isn't needed anymore. This becomes very tedious after a while. Maybe some people pass application contexts around to avoid this, but when you start seeing function signatures with 5-10 arguments of things they might (or did at one time) need, it all gets a bit onerous. In the real world when functional specs change, designs naturally evolve, so it's less about 'if it was designed right this wouldn't be a problem' - as in practice you cannot 100% future proof a design.


Would you ever write a non-trivial app that hard-codes file references: void parseRequest() {
string filename = "c:\data\foo\bar.dat''?
// do XML/JSON/binary/etc parsing here
}



To cite a real world example, you implement a developer mode where missing textures are replaced with a drop in missing texture replacement, so instead of a white missing texture (or the app refusing to launch), artists can get on with their job - with the missing textures highlighted. Worst case scenario you have a global 'LoadTexture' function to work with. You can implement this in a few lines within your texture loading routine, string filename = "missing_tex.bmp", and load that as the fallback. Bad design? I've seen this used in practice, and it's never caused a problem, never had to be changed, and I'm struggling to see how it's so bad.
Just one other thing, the following are all singletons (global state), but aside from the memory heap one, it's rare to see people passing this context information around.

- File system
- Coordinate systems (conceptually)
- Unit scales (radians vs degrees etc)
- Memory heap

There are definite reasons to pass in memory allocators for components that need restricted patterns of memory usage, but in many cases you can do just fine assuming a global heap. I can't see passing coordinate systems around as necessary, though there are some systems where it might well be required, because they need to support multiple coordinate systems. What threshold do people use to decide when it is acceptable to use singletons?
Agree with potatoman. My filesystem uses global fn like fileopen and fileclose, and so does soundsystem.
Resources are created using a global createresourceex().
They are not "pure" singleton , but still are globals and I feel very comfortable with them.
versus destroying and recreating the singleton before calling the unit test methods.[/quote]

Singletons cannot be destroyed, they don't have a lifecycle, they *are*. Just like you cannot destroy value '1'. It just is.

The very definition of a singleton is that it is and it's always the same.

Or they are not a singleton and they are just a big mess of a non-descript global state. Hence, singletons require completely destroying the entire test process for each test case, which makes it highly impractical, since they transform unit tests into integration tests.


This is second reason why they are bad. With a singleton you know what you have. Once they get created and destroyed during same process you no longer do, it's up to a whatever developer conjured up and called a singleton.

Things have a name for a reason and calling a potato a brick does nobody any favors.

To cite a real world example, you implement a developer mode where missing textures are replaced with a drop in missing texture replacement, so instead of a white missing texture (or the app refusing to launch),[/quote]

Which has nothing to do with singletons or global state.// stateful example
class AssetLoader {
private:
Texture defaultTexture;
public:
AssetLoader() {
....
defaultTexture = loadTexture("invalid_texture");
if (defaultTexture == null) throw FatalError;
}
// use same method to load either real or stand-in texture
Texture loadTexture(string name) {
...
return (realTexture == null) ? defaultTexture : return realTexture;
}
}


Alternative:// stateless example
// attempt to load resource by name, return defaultResource if not found
Resource loadResource(string name, Resource defaultResource);


Both of the above are testable in isolation and require no external state.

What threshold do people use to decide when it is acceptable to use singletons?[/quote]

When something exists for the duration of the process.

Garbage collector is an example. 'Process' is also a singleton.

Stateless objects or constants may be represented by a singleton since they survive duplication. When loading a value '1' it makes no difference if '1' and '1' are the same or different.

File system or database are not - they exists before/after process and are modified externally. File systems are rarely used as such anyway, typically a config file will point to some package (zip, physfs, jar) from which the stuff is loaded. Same for databases, one connects to a database, then works with that handle.

Anything with state should not be a singleton, that is when pain begins.

Memory heap[/quote]

Heaps will typically be partitioned, there's a reason why so many people are yelling out at memory fragmentation and default allocator. It's also a valid reason why pool allocators are used. Debug and other forms of memory allocators as well as third-party libraries also benefit from well-defined allocators.

Using third-party libraries which do not offer specific allocator override is a pain, most quality APIs give you control over that.

so does soundsystem.[/quote]

Which is great until you try to do automated testing on a headless server, which has no sound system and since it's hard-coded you cannot just replace it with dummy implementation. Then one starts mocking stuff and ends up with runaway duplicate auto-generated code.

This topic is closed to new replies.

Advertisement