Alternative to singleton

Started by
64 comments, last by ApochPiQ 12 years ago
I personally prefer a context object, from which managers/subsystems can be queried. In my system, every "fat" object (game entities and their sub-components, resources etc.) gets a context pointer upon construction, so it only needs to be separately passed to "thin" objects, if at all.

Another reason why to not use globals or singletons: suppose you one day want to integrate your app into a browser plugin. In that case you usually need to be able to support multiple instances of the app within the same plugin process, for multi-window / multi-tab support. With singleton subsystems you will have to be very careful of the different instances (likely running on different threads) not stepping on each others' toes.

In my case, I'd create a context object (with its own copy of each subsystem) for each instance within the process, and things should just work, provided that any third-party libraries used don't have hidden global state or singletons of their own.
Advertisement

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?

Leaving aside the overly dramatic 'suddenly' if you are telling your graphics system about your sound system or your sound about your graphics you are Doing It Wrong(tm).

Generally you wouldn't be doing this work in the renderer or in the sound system anyway; you would have a third entity which has an interface to both systems 'somehow' (the specifics depend on the engine, more than likely however upon construction is is granted a 'sound source' object to pull data from and has access to a 'renderable' which knows what to do with that buffer) and during the update phase pulls data from one location, does any processing required and pushes it to the other either via passing a pointer the whole block of data or maybe via a ring buffer interface. The point is the graphics system sees a chunk of data, the sound system never knows there the data is going and you have two loosely coupled systems with the data flow well designed.

The other option, granting the graphics system access to the sound system and then coming up with some way to pull the data and pass it on is just bad design as it suddenly requires your graphics system knows about sound, knows about processing sound, knows about the source of data when it has no reason to.

Same deal with heat haze; the value being produced for that is going to be a game driven system, it is just a value or maybe a buffer of values depending on the implimentation which is generated by a game side object and passed to the correct renderable component which does nothing more than that.

Passing parameters is GOOD.
It allows for clean interfaces, easy data flow and above all removes god aweful logic from areas it need not be in and generally makes your code better.
If you honestly think granting subsystems direct access to other subsystems for convoluted reasons such as this you have just failed Software Design 101.
(You've also just violated a few OOP design fundimentals such as the single responsiblity principle.)

Don't get me wrong, in my younger days I went down the 'zomg! Singletons solve passing parameter problems!' and embraced them like the foolish unexperiance developer I was... and then ran into a world of state, logic and maintaince pain as I had to update the software a little while later. Fortunately this taught me very early on that hiding dependancies and data flow is a god aweful idea and I produce better software design because of it.


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.
[/quote]

Purist wouldn't have given the module sound access anyway; entities which needed to play back sound would simply no longer query for sound interface objects; this could infact be a data change (our component system at work allows for this) requiring no direct code work at all.

But even when it comes to updating parameter you'll find that, most of the time, this simply isn't an issue IF you did it RIGHT to begin with which means sitting down for a while (at work this means a day or two at least) and think about what you object needs to do, what interfaces it requires and why.

A few times at work, yes, we have changed interfaces sometime adding, sometimes taking away, but this generally means that at worst we have to touch 2 or 3 high level constructors and write/delete some setup code for the thing we are adding/removing. Most of the time however is generally spent writing the new feature, not adding new places where it is referenced and the only time removal becomes a problem is when someone has written bad code in the first place.

End of the day things rarely 'suddenly' change and if they do then sometimes you need to take a step back and look at the object you have created anyway to see if it still does what it needs to do.

Memory heap


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.
[/quote]

Indeed, in practically every constructor for objects in our game/engine code which need to create memory on the heap the first parameter is an 'Allocator' object; due to our new/delete/alloc/dealloc macros the only way to allocate heap memory requires access to an allocator object reference.

This is also useful because you can tell from an object's function signature if it will ever allocate heap memory or not.

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.


It just strikes me as a debate on semantics. What is the difference between these two unit tests

FileSystem* sys = FileSystem::mount();
LoadFiles(sys);
FileSystem::unmount(sys);

FileSystem::mount();
LoadFiles();
FileSystem::unmount();

They are functionally identical, the latter has not been transformed into an integration test.. or if it has, then I fail to see the merit in making a distinction between the two. If you only ever have one file system, surely you wouldn't opt to pass in a file system to every possible routine that might need to load data.


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.


What is unclear about 'what you have' in the above code? The file system exists, or it doesn't. I'm not trying to be an arse here, I am genuinely struggling to understand what I seem to be missing when it comes to singletons, as some people get really passionate about this topic.


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


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.

[/quote]

If I have 30 places in code that call LoadTexture, where does 'AssetLoader' or 'Resource' come from? Yes, it would be nice if AssetLoader already existed and calls were conveniently piped through there already, but things rarely pan out that way - otherwise we wouldn't be having this discussion. As you know it's not about fixing those mere 30 lines of code, you have to work your way up the calling hierarchy for each instance, adding it as a parameter, which is not even easily possible with callback based load procedures from third party libraries.

Simple question, do you accept you could easily spend days updating a mature code base to use the above? Could that time have been better allocated elsewhere, is it really such a high risk proposition that it's worth spending the time to 'do it right'?


Stateless objects or constants may be represented by a singleton since they survive duplication.

File system or database are not - they exists before/after process and are modified externally.


The examples you provided earlier contained stateless constants, the hardcoded references I mean. I feel I might not fully understand what you mean by singleton. Databases might well not exist before/after, and certainly might not be modified externally. This comes back to my comment about semantics, is it really meaningful to classify a class based on what may or may not happen outside your runtime environment?


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.


It can be a pain, and third party libraries have a responsibility to provide a decent level of control. However if you're not building middleware, and your application doesn't need custom allocators, simply don't build in support for it. If you need it later on, add it.

Consider the time complexity of adding it later is (very) roughly O(n) based on the number of new and delete calls. The cost of adding it now and maintaining allocators is O(mn) where m is the number of times you refactor code with the additional overhead to deal with the allocators. So where you have people saying well it makes things easier down the track - sure it might only take 1 day to swap in a custom allocator because you built in support for it - but people conveniently forget about the weeks lost in maintaining that code for decades before actually needing that functionality. It is almost assured to be more efficient to defer this work, the only argument to build it in now is *if* there are some residual benefits in the meantime. If you live in a company with limitless resources, sure spend the time. More often than not though, there are deadlines to hit and far more important things to be concerned about, like competitors gaining an edge while you get bogged down handling fringe what-if's that might eventuate.


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.


What hard coding are we talking about here? SoundSystem::getInstance returns a sound system, which can be switched with a dummy implementation. Do you mean hard coded in a third party library?

SoundSystem::createDummyInstance();
DoSoundStuff();
SoundSystem::destroyInstance();



Just on the subject of template loading, this is something I'm going to have to be tackling myself soon. I would happily pass a LoadContext object around, I just wonder if that will be considered too singleton-like by some. For strong advocates of singletons, would you pass in a load context (which contains the XML data to load *and* a template manager or whatever else you wanted available at load), or pass in a separate TemplateManager object to each load call? Keep in mind we're talking hundreds if not thousands of load calls - and quite frankly, if I have to go in and update all those functions, there's a high probability it simply won't happen, we'll continue to put up with the burden of not having template functionality. This is the tradeoff I'm facing, and it seriously bothers me that some useful feature might get missed because of the attitude 'if you're going to do feature A, what if you need B, or C down the track?'. If I can put in support for B or C with little cost, sure, but otherwise I just need A, and someone can worry a bout B or C later...

Leaving aside the overly dramatic 'suddenly' if you are telling your graphics system about your sound system or your sound about your graphics you are Doing It Wrong™.
Generally you wouldn't be doing this work in the renderer or in the sound system anyway; you would have a third entity which has an interface to both systems 'somehow' (the specifics depend on the engine, more than likely however upon construction is is granted a 'sound source' object to pull data from and has access to a 'renderable' which knows what to do with that buffer) and during the update phase pulls data from one location, does any processing required and pushes it to the other either via passing a pointer the whole block of data or maybe via a ring buffer interface. The point is the graphics system sees a chunk of data, the sound system never knows there the data is going and you have two loosely coupled systems with the data flow well designed.

The other option, granting the graphics system access to the sound system and then coming up with some way to pull the data and pass it on is just bad design as it suddenly requires your graphics system knows about sound, knows about processing sound, knows about the source of data when it has no reason to.

Same deal with heat haze; the value being produced for that is going to be a game driven system, it is just a value or maybe a buffer of values depending on the implimentation which is generated by a game side object and passed to the correct renderable component which does nothing more than that.


I'm sorry I should have perhaps elaborated. I fully understand the above, I did not mean to say your graphics engine would have a direct dependency on the sound engine, or dynamic model data of temperatures. There would be some interfaces defined in the graphics engine to provide the data it needed (wave form interface, whatever), but where that data actually came from, who implemented these interfaces - the sound engine, or some other component, well the graphics engine wouldn't be privvy to that information. The question is when my graphics engine entity springs into existence, it needs this interface - how does it resolve it. Conceptually this kind of thing would be linked up in an editor, a user drags a link to the data source - so you would have some kind of GUID defining the 'what'. Push or pull style interface depends on how much processing is involved to get the data into a suitable graphics engine format.

So, what are the thoughts on this style of load?

LoadGraphicsEntity(LoadContext& context)
{
...


// resolve waveform interface
IWaveFormInput* wfi = context.ResolveInterface<IWaveFormInput>(guid);
}

What I've seen a lot of, is passing in many 'manager' or factory type objects, to cater for each possible type of load type that might need to be handled. This problem is compounded when registering a third party load callback called when it hits specific XML nodes, because you don't get to add parameters (easily) to a third party load callback.

So conceptually we have a lookup directory where we can resolve a waveform data provider by it's application unique GUID, where does the handle to that manager come from? Alternatives to the singleton approach of LoadContext::getInstance().ResolveInterface, I mean (which is what the above attempts to address).



Passing parameters is GOOD.
It allows for clean interfaces, easy data flow and above all removes god aweful logic from areas it need not be in and generally makes your code better.
If you honestly think granting subsystems direct access to other subsystems for convoluted reasons such as this you have just failed Software Design 101.
(You've also just violated a few OOP design fundimentals such as the single responsiblity principle.)


The graphics engine is responsible for visualising the environment, I don't think it's reasonable to classify the graphics engine needing specialised information as convoluted. Usually it's dynamics information, but in practice it could be from many areas.

Is a constructor with over 50 arguments 'better code'? You might think I'm making this up, but I have seen this in a framework before, because somebody had (apparently) been told it 'makes code better' to pass parameters. For the same reason this specific instance doesn't mean passing parameters is bad, people need to be careful not to cherry pick code and present that as an argument for why code is 'generally better' when it was just a case of general poor design. Which is why I've asked for alternatives, and truly I will take on board any advice.

Is a constructor with over 50 arguments 'better code'?

No it is a code smell that could indicate the class is doing too much.

If I have 30 places in code that call LoadTexture, where does 'AssetLoader' or 'Resource' come from?

I think the real question is why are you calling LoadTexture in 30 places? People often try to give too much responsibility to a class/object, like dmail suggests.

But I'll repeat what I said. Singletons ensure that there is ever only one object in existence, as anything else would be considered a fatal error. There are things we deal with in the world that have a singleton-ish nature (you mentioned URIs, Antheus mentioned actual object instances, etc.), but that rarely translates into code very well. So far you've argued in favor of having a global object, not a singleton.

Singletons should not be used because module A needs something from module B, and it's easier to just make a singleton than pass a reference. That's what globals are for, and even then, globals aren't a great way to do things. You want to multithread things? Good luck dealing with your globals, and heaven help you should there be a bug somewhere and you have to try and figure out which thread changed the global state that's affecting the rest of the program. That reason alone is enough for me to avoid globals.

And for your example about the FileSystem... what even is a FileSystem object? What does it even represent? Sure, you can refer to a URI as a type of singleton (as was said), but that's very different than making your resource loader or file system object (whatever that is) a singleton.

There are rare exceptions when singletons might be necessary. Like when developing a driver for a particular piece of hardware that only has the capability to interact with one driver interface and not two. But I have yet to see in a game engine when a singleton is really needed. From what I've seen, it's just a developer thinking "Globals are bad and OOP is good, so if I use a class that has a global state (and for some reason, they get the idea that they need to make it so that there can only ever be one instance, so they do extra work and go out of their way to ensure that there is only ever one instance) it's good, but if I used a global in a namespace that's bad." When in reality, a static in a class is nothing more than a global in a namespace. You've just gone through some extra effort to ensure there's only ever one instance of said object, when there really isn't any justification for such strict rules (just because you won't ever be using more than one instance doesn't mean you should enforce strict ruling that there cannot ever be more).
[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 ]
The problem with 99% of 'pro-singleton' arguements is the examples pulled out of thin air such as constructors with 50 parameters and calling something from 30 places when the truth of the matter is those examples are not examples of 'good singleton usage' but of bad code design made worse by introducing yet more coupling of state.

Sure, we have some constructors at work which take 7 or 8 parameters for large systems, although they are all pretty much flagged for 'ugh; refactor!' once we have time; and yes we have had code where something is passed down the line and called for 7 or 8 places but that also suffers the same fate (I recently pulled out a system which was setup just like that and replaced it with a much cleaner solution with only 2 call sites after some refactoring work and now the dependancies are clear, the other classes have less responsibility (yay!) and the code is easier to follow).

I think the real question is why are you calling LoadTexture in 30 places? People often try to give too much responsibility to a class/object, like dmail suggests.

This is the code base you inherit, I don't see how it's material to this discussion. Someone needed to load a texture, LoadTexture was called. We have to work within the constraints we're given, which means you don't always get to design a system from scratch. So what do you do under these constraints, do you just dump it in the too hard basket, because you can't make your change as cleanly as you'd like?


But I'll repeat what I said. Singletons ensure that there is ever only one object in existence, as anything else would be considered a fatal error. There are things we deal with in the world that have a singleton-ish nature (you mentioned URIs, Antheus mentioned actual object instances, etc.), but that rarely translates into code very well. So far you've argued in favor of having a global object, not a singleton.

Reading the OP I think we all understand what this thread is about, and if that's a global object, so be it. I'll use the term global single instance from this point to resolve any ambiguity.


Singletons should not be used because module A needs something from module B, and it's easier to just make a singleton than pass a reference. That's what globals are for, and even then, globals aren't a great way to do things. You want to multithread things? Good luck dealing with your globals, and heaven help you should there be a bug somewhere and you have to try and figure out which thread changed the global state that's affecting the rest of the program. That reason alone is enough for me to avoid globals.

It goes a little deeper than simply "it's easier". Sometimes whether a feature gets implemented hinges on the associated cost benefit analysis, it may not feasible to touch code all over the place to get in what is, at a fundamental level, a trivial 10 line change. If that feature gets scrapped because 'you might need two of these later, or threading might be hurt', I think you're doing a real disservice to your client or customers.

Now if you have a block of code that accesses a global instance, and you modify things to have that same object passed in by reference - I fail to see how this makes any difference in terms of threading stability. Same object, referenced at the same point in code, referenced at the same point in time - the only difference is how the reference was acquired. Even more insidious is when your programmers start storing references to what are effectively global state classes that were passed in to their constructor (or some method) - you don't even have a central area where you can debug which threads are accessing the class anymore. In many respects these type of member references could be considered pseudo globals, admittedly it's not something I've seen discussed much.


And for your example about the FileSystem... what even is a FileSystem object? What does it even represent? Sure, you can refer to a URI as a type of singleton (as was said), but that's very different than making your resource loader or file system object (whatever that is) a singleton.

It was for illustrative purposes. If you had to implement mapped network drives on a platform that didn't support this, would you add in some global state at the point where you intercept file IO level requests, or update your entire framework to pass around a FileSystem object, which contained this state? Specifically, would you consider the merits and drawbacks of each approach, or simply go for the more OO approach, no questions asked?


You've just gone through some extra effort to ensure there's only ever one instance of said object, when there really isn't any justification for such strict rules (just because you won't ever be using more than one instance doesn't mean you should enforce strict ruling that there cannot ever be more).

Well if you designed the class without considering the possibility there might be two instances of them, I'd say you have a real good reason for enforcing such a strict rule. Just like if you designed a program without multithreading in mind..

The problem with 99% of 'pro-singleton' arguements is the examples pulled out of thin air such as constructors with 50 parameters and calling something from 30 places when the truth of the matter is those examples are not examples of 'good singleton usage' but of bad code design made worse by introducing yet more coupling of state.

I'm not anti singleton, but I'm not sure this makes me pro singleton, I prefer singleton agnostic. I explicitly stated that the 50 parameter constructor was not a pro singleton argument, and I don't think it's fair you characterise it as such. I don't know what to say about the calling something from 30 places comment, if anything I was being conservative. That's not the point though, because if your argument is there is no stateful function that should be called from over 30 places, well frankly I don't see how you can make that assertion. I certainly can't judge the quality of code based on the frequency of function call signatures (though it would make code reviews easier..).

All that said, what I really want to discuss here is alternatives. You can say something is bad until you're blue in the face, but unless a suitable alternative is provided, you run the real risk of forcing an inferior implementation. I've never read a discussion on passing context objects around, and it seems like a suitable middle ground, though few have cared to comment one way or another on this..

This topic is closed to new replies.

Advertisement