• Advertisement
Sign in to follow this  

my engine, mutually nested objects- bad?

This topic is 3666 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This little game engine I'm working on has generic Monster objects ( essentially they represent characters in the game). There is also the Monster object manager, which is like a collection and factory for the monster objects. The monster object class contains certain properties that are common for all game monsters and an event handler. All game monsters have the same events. What the monster actually does during a particular event is determined by a monster behavior object. The monster behavior class is implemented as a C# interface where each method in the interface is called when a specific event is fired. The basic idea is for each type of monster in the game, you implement a monster behavior class, filling-in different code for each event. Then you pass it to the monster object manager and it produces the resulting monster object. The funny thing is that I want to expose the monster objects attributes to the behavior object, so for each event I pass a monster object reference. When the monster object calls an event it passes a reference to itself to it's behavior object, at which point the monster and its bevior object are mutually referencing each other. It seems to work fine, but I have a sense that there must be something terribly wrong with this arrangement. Call it intuition. :) Any one have a more sensible way to do this? Basically I'm just trying to write this library to internalize everything, programming-wise, except for the monster's behavior. Also when I go to get my monsters to query the properties of other monsters, I suspect it will be equally inappropriate for each monster object to contain a reference to to the monster object manager, which contains references to all of the monster objects.

Share this post


Link to post
Share on other sites
Advertisement
There's nothing inherently wrong with having a bi-directional pointer, it's a downright requirement in some situations.

In your case, separating the behaviour from the "object to be behaved" seems like an ok idea, and seems to require both objects to point to each other at some point in time.

Share this post


Link to post
Share on other sites
It's a common way of doing things. It introduces a cyclic dependency, which is not the awesomest thing in the world if you're an obsessive-compulsive OO designer, but it's not going to cause any runtime problems. One exception: If you're using ref-counted smart pointers for the two references, you'll need to make one of them weak in order to avoid a cyclic reference which would lead to a memory leak.

Share this post


Link to post
Share on other sites
Quote:

... which is not the awesomest thing in the world if you're an obsessive-compulsive OO designer


There's the problem. I'm always subconsciously thinking that everything must be strictly hierarchical or else it's not going to work.

Share this post


Link to post
Share on other sites
For events you almost cannot avoid it - you need to know when either producer or consumer goes away (especially if it's deleted). In which case, unless you imply some really rigid framework around it, each needs reference to other. At least if you're using ref-counted or manually managed pointers.

The only way around it is using garbage collection (not ref-counted type), but that can be impractical in C++.

Bigger concern in case of events may be memory impact. The number of references retained can quickly grow, trashing your heap, and causing lots of overhead with (de)registration.

But that's just a side-effect of event-driven design.

Share this post


Link to post
Share on other sites
Man, lately it seems that every time a good question comes up, I'm fresh from reading something distinctly relevant. Perhaps its a testament to the quality and quantity of material I've been reading, perhaps its that, to borrow from an old adage, "When all you've got is a hammer, everything starts to look like a nail" or in this case, "When you've just read about a hammer, everything starts to look like a nail."


In any event, I think what I'm about to say will put things into a more pleasant light for the OO-obsessed...

Quote:
Interface Principle from Exceptional C++, Item 32
For a class X, all functions, including free functions, that both

  • "Mention" X

  • Are "Supplied with" X


are logically part of X, because they form part of the interface of X.


It follows in Item 33 that if you apply this same principle to a member function belonging to a class A, which takes as a parameter another class B, it follows that the function is a part of B and also, because it takes an implicit A* as a parameter, that B depends on A. Since A also depends on B, the classes are interdependent.

This interdependence is a known and accepted part of the C++ language, and is supported by Koenig lookup, which, in short, expands the scope of name resolution to include the scope in which its parameterized types exist if no potential matches are found in closer scopes (local scope, class scope).

What this argument supports is that, even though Monster and Behavior are distinct classes, Behavior is, in fact, part of the interface of Monster (albeit, perhaps a private one). It's simply encapsulated in a manner which makes a different, but equivalent, implementation easy to substitute.

This "part of" relationship is recognized in C++, though still not as strong as the relationship between a class and its member functions (which benefit from automatic access to Monster's internals). In fact, if you were to define Behavior as a friend of Monster, then a function in Behavior taking a Monster parameter and an implicit Behavior parameter is functionally equivalent to an equivalent function in Monster taking a Behavior parameter and an implicit Monster parameter where Monster has been made a friend of Behavior. By taking this example one step further and making Behavior an inner class of Monster, you can see that it is indeed true that Behavior is a part of Monster. This exposes the fact that, due to the nature in which you have chosen to implement this relationship (namely, in a way that supports run-time binding of behaviors) you have chosen to make Behavior external to Monster itself only as an implementation detail (In fact, you could keep run-time binding with an inner class implementation if it were acceptable to have the entire Behavior hierarchy within Monster, but I'd argue that you've made the better decision by separating responsibilities into separate bodies, if not interfaces.)

It also follows that Behavior could also be made a true inner class if it were bound at compile time, such as if Behavior were supplied as a template parameter to an instantiation of Monster.

In short, there is nothing wrong with interdependence if the interdependent bodies form a single interface.

Share this post


Link to post
Share on other sites
Your solution seems very sensible to me. A Monster needs to know what its Behaviour is, and a Behaviour needs to know what Monster it is dealing with at this moment.

Share this post


Link to post
Share on other sites
Why not pass in the attributes that are relevant to the action instead of the entire class? Another way to resolve it is to create a monster attributes class. Then compose the monster class of an attributes class and a behavior class. A third way to resolve the issue is to move the attributes to the behavior class. Either of the second two improves separation of concerns.

Share this post


Link to post
Share on other sites
Quote:

Why not pass in the attributes that are relevant to the action instead of the entire class?


I could (I might be able to) put all of the accessible attributes in a class by themselves. Then I'd have a monster object which contained the attributes object and the behavior object. So the monster object would do the event logic and pass the attributes object to the behavior object. That way the the behavior object would have a reference to the attributes and it would not be necessary for the attributes to reference the behavior.

That might placate my OCD object oriented tendencies.

Share this post


Link to post
Share on other sites
Although the relationship between the the monster object and the monster object manager will still be circular so I appreciate the advise on how to deal with that situation.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement