Sign in to follow this  
nsto119

Some questions about proper OO design.

Recommended Posts

Is there any reason whatsoever to create a class for an object you'd only ever want one instance of? Say, for example, I had a.... ResourceManager class. There's no reason I'd ever want more than one of those (okay, so that's probably not entirely true, but that's beside the point). Is there really any compelling reason to make that a class instead of just a collection of functions? It's possible and easy to hide the data the ResourceManager needs without using a class. I'm aware of the Singleton design pattern, but that doesn't really seem to offer any advantages either.

Share this post


Link to post
Share on other sites
Making it a class will allow you to much more readily control which code is using it. Any code can call a free or public static member function - the function is effectively 'global' - so if you want to change that function, it's harder to predict what the effects are going to be.

If, on the other hand, it's a non-static member function, then only code that has been passed the instance of the class will be able to call it. The function is not global; you're explicitly passing around a "token" that allows code to call those functions, and you can keep control of it accordingly.

Share this post


Link to post
Share on other sites
Quote:
Original post by nsto119
Is there any reason whatsoever to create a class for an object you'd only ever want one instance of?

Say, for example, I had a.... ResourceManager class. There's no reason I'd ever want more than one of those (okay, so that's probably not entirely true, but that's beside the point). Is there really any compelling reason to make that a class instead of just a collection of functions? It's possible and easy to hide the data the ResourceManager needs without using a class.


Strange, a post where someone doesn't see the merits of Singletons. Surprising...


Free functions are fine. They don't however allow multiple instantiations. They also don't provide you with encapsulation, so people can alter the internal state. A class at least provides ability to hide that.

Your ResourceManager might only be one. But you'll also need a FooManager and BarManager and ....

If you choose to go with free functions, you'll find yourself unable to re-use perfectly good code.

A class also gives you options to change the behavior later, or expand on it, such as add TextureManager, PlayerManager, both of which share most of same behavior.

Share this post


Link to post
Share on other sites
Quote:
Original post by superpig
Making it a class will allow you to much more readily control which code is using it. Any code can call a free or public static member function - the function is effectively 'global' - so if you want to change that function, it's harder to predict what the effects are going to be.

If, on the other hand, it's a non-static member function, then only code that has been passed the instance of the class will be able to call it. The function is not global; you're explicitly passing around a "token" that allows code to call those functions, and you can keep control of it accordingly.


That might be true, but there's really nothing stopping anything from creating a new instance of the class either, which might be useless, but I suppose could be harmful in some situations.


Sometimes there ARE functions that everything needs access to. Classes remove this possibility unless you want to start using globals.

Share this post


Link to post
Share on other sites
Quote:
Original post by nsto119


Sometimes there ARE functions that everything needs access to. Classes remove this possibility unless you want to start using globals.


Static free functions *are* globals.

Non-global functions cannot hold state.

Share this post


Link to post
Share on other sites
Quote:
Original post by nsto119
That might be true, but there's really nothing stopping anything from creating a new instance of the class either, which might be useless, but I suppose could be harmful in some situations.
There are ways of preventing that separately - the most obvious one would be to use a class factory that keeps track of the number of instances of the class that have been created, and refuses to create new ones beyond that limit. Something like this:


class MyRestrictedObject
{
protected: MyRestrictedObject() { /* ... */ }

public: static MyRestrictedObject* CreateInstance()
{
static int numInstances = 0;
if(numInstances >= 1) return NULL; // or throw an AlreadyCreatedException
++numInstances;
return new MyRestrictedObject();
}
};







Quote:
Sometimes there ARE functions that everything needs access to.
Yes, but they're a lot less common than you'd think. I frequently see people claim that they need things like a ResourceManager to be global, but that's really not true - only code that is actually working with resources (for example, your renderer, sound system, etc) really needs access. Your physics code or gameplay code generally does not.

Which specific modules are you considering this for, anyway?

Quote:
Classes remove this possibility unless you want to start using globals.
Or singletons. This is true, but it's a box you can "break open" later - for example, by turning the class into a singleton; all your existing code that passes the object around will still continue to work happily. The inverse - unstitching a module from the globally accessible space - is harder, because you've got to visit every site where the module is referenced and figure out how you're going to get the object reference to that site.

Share this post


Link to post
Share on other sites
Quote:
Original post by nsto119
Is there any reason whatsoever to create a class for an object you'd only ever want one instance of?

1. You can't be sure what happens in the future
2. Where is the harm in making something a class and create just one instance (for now)?
3. Inheritance and Polymorphism only work with classes
4. Globals are the devil

Quote:
Original post by nsto119
I'm aware of the Singleton design pattern

5. The Singleton is the devil disguised with deceiving OO-sunglasses to lure you into chaos

Share this post


Link to post
Share on other sites
Quote:
Original post by superpig
Which specific modules are you considering this for, anyway?


Basically just a Quake style console. I need just about everything to access it for debug output.

Share this post


Link to post
Share on other sites
@ superpig:


public: static MyRestrictedObject* CreateInstance()
{
static int numInstances = 0;
if(numInstances >= 1) return NULL; // or throw an AlreadyCreatedException
++numInstances;
return new MyRestrictedObject();
}




Isn't numInstances = 0 in the wrong place? Won't this just set numInstances to 0, fail the if statement, increment numInstances to 1, and return a new MyRestrictedObject() every time?

Share this post


Link to post
Share on other sites
Quote:
Original post by Shakedown
@ superpig:

*** Source Snippet Removed ***


Isn't numInstances = 0 in the wrong place? Won't this just set numInstances to 0, fail the if statement, increment numInstances to 1, and return a new MyRestrictedObject() every time?

Local static variables are initialized on first use only, and preserved between function calls.

Share this post


Link to post
Share on other sites
Hmm... the console itself isn't really what they need to access, though, right? It's more that that need a trace writer of some sort:


class ITraceWriter
{
public: virtual void Trace(std::string message) = 0;
};




The console will have other functionality for things like rendering itself that they don't need access to.

I'd suggest that allowing the use of multiple trace writers is a very good idea, even if they all end up writing their messages into the same console object. Think about it - if your renderer is using a different trace writer to your game objects, then (a) you can turn off one group of messages while keeping the other, (b) you can automatically have the writer attach 'RENDERER: ' or 'GAMEPLAY: ' to the beginning of messages, (c) you can make it so that renderer 'info' messages are only displayed when you're in a special renderer debugging mode, etc...

Passing the objects around doesn't have to be that laborious. For example: many of your gameplay objects share a common base class, right? You can store a reference to the writer in that base class, and require it as part of the constructor, and then it'll be in scope for any member function on a game object.

Edit: @Shakedown: yeah, Brother Bob's nailed it perfectly.

Share this post


Link to post
Share on other sites
Quote:
Original post by superpig
Hmm... the console itself isn't really what they need to access, though, right? It's more that that need a trace writer of some sort:

*** Source Snippet Removed ***

The console will have other functionality for things like rendering itself that they don't need access to.

I'd suggest that allowing the use of multiple trace writers is a very good idea, even if they all end up writing their messages into the same console object. Think about it - if your renderer is using a different trace writer to your game objects, then (a) you can turn off one group of messages while keeping the other, (b) you can automatically have the writer attach 'RENDERER: ' or 'GAMEPLAY: ' to the beginning of messages, (c) you can make it so that renderer 'info' messages are only displayed when you're in a special renderer debugging mode, etc...

Passing the objects around doesn't have to be that laborious. For example: many of your gameplay objects share a common base class, right? You can store a reference to the writer in that base class, and require it as part of the constructor, and then it'll be in scope for any member function on a game object.

Edit: @Shakedown: yeah, Brother Bob's nailed it perfectly.


Can you elaborate on this "trace writer" concept a little bit? I'm not entirely sure what you mean but it sounds interesting.

There's something I don't really like about passing the same object to a constructor for each instance though. Usually in that case, I store a static reference in the base class and have static function that sets the reference before any objects are created

Share this post


Link to post
Share on other sites
Quote:
Original post by nsto119
Can you elaborate on this "trace writer" concept a little bit? I'm not entirely sure what you mean but it sounds interesting.
Sure thing.

The basic idea is to think about your objects in terms of what you need them to do, instead of in terms of what they actually are. Emitting trace messages (a.k.a. log messages) from your app doesn't require "a console" - it just requires an object that is capable of being given trace messages, an object that I'm calling a "trace writer."

By writing your code in a way that specifies only what you need and nothing more, you make the code very general and flexible. You could replace your console object with any other object that can be treated as a trace writer. It's easiest to see this kind of thing when you're dealing with type variables (like templates, but less crude) - given:


// X is some unknown type
X a, b;
return a + b;


X doesn't have to be a number type, it just has to be "any type that supports the notion of addition." It could be strings, or vectors, or lists.

So, ITraceWriter distils the bare minimum you need from the object, allowing a great many objects to possibly fill that need. For example, logging to a file:


class LogFile : public ITraceWriter
{
private: std::ofstream out;
public: LogFile(std::string filename) : out(filename) { }
public: virtual void Trace(std::string message) { out << message << std::endl; }
}



LogFile objects are interchangeable with each other, and for things that only need an ITraceWriter, they're interchangeable with other ITraceWriters. You could make objects trace to a file instead of the console, or to file A instead of file B, by just giving them a different object to trace to:


void DoSomeStuff(ITraceWriter& trace)
{
/* ... do stuff ... */
trace.Trace("Something is rotten in the state of Denmark!");
/* ... more stuff ... */
}

// Do it while logging to the console
QuakeStyleConsole console; // QuakeStyleConsole implements ITraceWriter, natch
DoSomeStuff(console);

// Do it while logging to file A.txt
LogFile a("a.txt");
DoSomeStuff(a);

// Do it while logging to file B.txt
LogFile b("b.txt");
DoSomeStuff(b);



DoSomeStuff can trace to any object "that can be traced to" (i.e. that supports ITraceWriter).

Quote:
There's something I don't really like about passing the same object to a constructor for each instance though.
It can be a little annoying, I'll grant you, but there are some techniques you can use to cut down the effort:

  • Each object doesn't necessarily have to have its own trace writer, it merely needs access to one. Say you're looking at a particle effect that is attached to a game object; the particle effect could simply use objectImAttachedTo.tracer instead of having its own tracer.

  • If you're using a factory of some sort to create objects, it could provide the writer to objects for you. For example, say you're creating game objects from their string name - entitySystem.CreateEntity("enemy_grunt"), etc - you could have the entitySystem automatically pass its trace writer to the objects it creates.

  • If you're dealing with thousands of tiny, self-contained, utility objects - e.g. a Vector3D class - then it may be worth using a static pointer to an ITraceWriter, as you said - but keep it private. A static member variable is a global, and has all the drawbacks of such, but at the very least you can restrict it to use by a single class.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this