views:

61

answers:

2

I'm having a little circular-dependency problem. It works fine, but it makes for ugly-seeming code. It's in the context of a Snake game.

I have a class, Snake, which contains a vector of SnakeSegments, and manages their interaction (for instance moving and growing as a unit, rather than as separate entities).

When a SnakeSegment collides with a Food object, it flips its hasEaten member to true. The Snake routinely queries the SnakeSegments, essentially for this member. If any of the queries return positive (i.e. one has hit food), then the Snake will grow as a unit (i.e. expand the head and shrink the tail). This is all fine and good, but I'd much prefer a more signal-based approach, where, when a SnakeSegment hits food, it sends an alert (signal, interrupt, etc.) to the Snake class, which tells it to grow. This means I wouldn't have ugly code in my Snake's Update function, checking all the segments; and I would instead have an OnEat() function in my Snake class.

However, this leads to circular dependencies; the Snake contains a vector of SnakeSegments, and the SnakeSegments have a Snake& or Snake* member, which tells them whom to alert when they eat. In the code, I essentially just have to pre-declare the Snake class:

class Snake;
class SnakeSegment
{
...
Snake* alertOnEat;
...
};

and my Snake class just works normally

#include "SnakeSegment.hpp"

class Snake
{
...
std::vector segments;
...
void OnEat();
...
};

Are there any nicer designs to this? Note that it isn't just a problem occuring here; a similar problem occurs in a number of areas (e.g. the GameWorld contains a Snake member, and the Snake alerts the GameWorld when it dies), so a solution specific to Snake and SnakeSegment isn't what I'm looking for.

Thank you!

+3  A: 

What you may want to look at for this problem is the Observer/Observable design pattern. It allows you to create objects that observe (Snake) observable objects (SnakeSegment) and get notified right away when their state has changed.

Wikipedia has a good example of it written in many languages including C++.

It's a common pattern used in a lot of GUI development so the View layer can change when any of the underlying Model data changes.

MadcapLaugher
+4  A: 

Your current design is perfectly fine in most situations. Yes, it creates a circular dependency, but it also is the simplest and the clearest way to do it.

However, you should limit those dependencies between interfaces or base classes rather than to the derived classes directly. The world-snake dependency is a good example. You probablly don't necessarily want the World class to know about all the possible game object types. However, what you can do is derive all your game objects from a common GameObject class and make World and GameObject interdependant.

There are also more complicated ways to avoid dependencies all with their pros and cons, they mostly differ on the level of separation and clarity. Among those, function pointers, delegates, observers, listerners, functors, etc.

In the end, it always come down to how much complexity you are ready to introduce to your architecture in exchange of flexibility and clear separation of concerns.

Never forget that design is compromise.

Coincoin