Jump to content

  • Log In with Google      Sign In   
  • Create Account


Alternative to singleton


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • This topic is locked This topic is locked
65 replies to this topic

#1 BattleMetalChris   Members   -  Reputation: 157

Like
0Likes
Like

Posted 15 March 2012 - 10:07 AM

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?

Sponsor:

#2 Net Gnome   Members   -  Reputation: 769

Like
0Likes
Like

Posted 15 March 2012 - 10:18 AM

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.

#3 Telastyn   Crossbones+   -  Reputation: 3724

Like
4Likes
Like

Posted 15 March 2012 - 10:22 AM

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.

#4 Cornstalks   Crossbones+   -  Reputation: 6966

Like
4Likes
Like

Posted 15 March 2012 - 10:29 AM

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

#5 Antheus   Members   -  Reputation: 2393

Like
6Likes
Like

Posted 15 March 2012 - 10:31 AM

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[i] != b[i]) 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


#6 BattleMetalChris   Members   -  Reputation: 157

Like
0Likes
Like

Posted 15 March 2012 - 11:59 AM

Antheus, that's a fantastic explanation, thank you :)

#7 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 17 March 2012 - 11:31 PM

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.

#8 Potatoman   Members   -  Reputation: 108

Like
1Likes
Like

Posted 17 March 2012 - 11:55 PM

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?

#9 Giallanon   Members   -  Reputation: 1069

Like
0Likes
Like

Posted 18 March 2012 - 07:31 AM

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.


#10 Antheus   Members   -  Reputation: 2393

Like
0Likes
Like

Posted 18 March 2012 - 07:58 AM

versus destroying and recreating the singleton before calling the unit test methods.


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


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?


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


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.


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.

#11 AgentC   Members   -  Reputation: 1231

Like
0Likes
Like

Posted 18 March 2012 - 08:20 AM

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.

Every time you add a boolean member variable, God kills a kitten. Every time you create a Manager class, God kills a kitten. Every time you create a Singleton...

Urho3D (engine)  Hessian (C64 game project)


#12 phantom   Moderators   -  Reputation: 6801

Like
1Likes
Like

Posted 18 March 2012 - 09:05 AM

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

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.


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.

#13 phantom   Moderators   -  Reputation: 6801

Like
0Likes
Like

Posted 18 March 2012 - 09:10 AM

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.


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.

#14 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 18 March 2012 - 09:24 AM

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.


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

#15 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 18 March 2012 - 10:00 AM

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.

#16 djofdifjpodjfpodkpofdpofpd   Members   -  Reputation: 120

Like
3Likes
Like

Posted 18 March 2012 - 10:44 AM

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

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

#17 Cornstalks   Crossbones+   -  Reputation: 6966

Like
0Likes
Like

Posted 18 March 2012 - 05:24 PM

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

#18 phantom   Moderators   -  Reputation: 6801

Like
1Likes
Like

Posted 18 March 2012 - 06:24 PM

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

#19 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 19 March 2012 - 08:08 AM

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

#20 Potatoman   Members   -  Reputation: 108

Like
0Likes
Like

Posted 19 March 2012 - 08:14 AM

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




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS