views:

37

answers:

2

I'm writing a game that has many components. Many of these are dependent upon one another. When creating them, I often get into catch-22 situations like "WorldState's constructor requires a PathPlanner, but PathPlanner's constructor requires WorldState."

Originally, this was less of a problem, because references to everything needed were kept around in GameEngine, and GameEngine was passed around to everything. But I didn't like the feel of that, because it felt like we were giving too much access to different components, making it harder to enforce boundaries.

Here is the problematic code:

    /// <summary>
    /// Constructor to create a new instance of our game.
    /// </summary>
    public GameEngine()
    {
        graphics = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));

        //Sets dimensions of the game window
        graphics.PreferredBackBufferWidth = 800;
        graphics.PreferredBackBufferHeight = 600;
        graphics.ApplyChanges();

        IsMouseVisible = true;
        screenManager = new ScreenManager(this);

        //Adds ScreenManager as a component, making all of its calls done automatically
        Components.Add(screenManager);

        // Tell the program to load all files relative to the "Content" directory.
        Assets = new CachedContentLoader(this, "Content");

        inputReader = new UserInputReader(Constants.DEFAULT_KEY_MAPPING);

        collisionRecorder = new CollisionRecorder();

        WorldState = new WorldState(new ReadWriteXML(), Constants.CONFIG_URI, this, contactReporter);

        worldQueryUtils = new WorldQueryUtils(worldQuery, WorldState.PhysicsWorld);

        ContactReporter contactReporter = new ContactReporter(collisionRecorder, worldQuery, worldQueryUtils);

        gameObjectManager = new GameObjectManager(WorldState, assets, inputReader, pathPlanner);
        worldQuery = new DefaultWorldQueryEngine(collisionRecorder, gameObjectManager.Controllers);
        gameObjectManager.WorldQueryEngine = worldQuery;

        pathPlanner = new PathPlanner(this, worldQueryUtils, WorldQuery);

        gameObjectManager.PathPlanner = pathPlanner;

        combatEngine = new CombatEngine(worldQuery, new Random());
    }

Here is an excerpt of the above that's problematic:

        gameObjectManager = new GameObjectManager(WorldState, assets, inputReader, pathPlanner);
        worldQuery = new DefaultWorldQueryEngine(collisionRecorder, gameObjectManager.Controllers);
        gameObjectManager.WorldQueryEngine = worldQuery;

I hope that no one ever forgets that setting of gameObjectManager.WorldQueryEngine, or else it will fail. Here is the problem: gameObjectManager needs a WorldQuery, and WorldQuery needs a property of gameObjectManager.

And, aside from that issue, it's a maintainability mess, because if the constructors aren't called in a specific order, the program will crash.

What can I do about this? Have I found an anti-pattern?

+5  A: 

Circular class dependencies aren't always a design error, but in this case I'd say that you're asking for trouble.

The problem appears to be that you need the Controllers from this GameObjectManager to initialize some other dependency of GameObjectManager. But why does the GameObjectManager have to be the one to create the controllers? If more than one thing depends on them, then you should probably have something like a ControllerFactory that allows you to initialize that dependency separately, and pass it to the GameObjectManager via the constructor.

Judging by the incredibly generic-sounding name, GameObjectManager, I'd say you're dealing with a God Object here. At the very least you have Sequential Coupling, and that's not a good thing.

As for what you can do about it, that's hard to say without knowing exactly what the GameObjectManager does, but you should try to do what's mentioned above - have something else responsible for creating the "controllers" and supply those as a dependency to the GameObjectManager, rather than having that monolithic object be the one responsible for creating them.

Aaronaught
Ok, that works fine for the excerpt I posted, because `GameObjectManager` itself isn't a dependency, but one of its properties is. But what about when two objects require an instance of each other, and cannot be instantiated without it? (So, if all of `GameObjectManager` were required, and not just `GameObjectManager.Controllers`)
Rosarch
@Rosarch: What you're referring to is called a cyclic dependency. Generally, this is considered a design error, with the notable exceptions of recursive data structures and helper objects that depend *only* on the same object that is responsible for creating and managing them (such as a resource pool). In other cases, when you have a cyclic dependency, there is usually a subset of functionality in one or both of the dependencies that can be refactored into its own class, so that instead of having two classes depending on each other, they each depend on the third (new) class.
Aaronaught
I would also like to point out that in my game's terminology, `GameObject` refers to an entity in the world, like a tree or the player. It doesn't mean object in the OOP sense.
Rosarch
A: 

You can create a context object as follows:

  • You define a Factory interface
  • You define a key the uniquely identifies each component
  • For each of your components you register an implementation of the Factory interface (that creates the component) with the context.
  • The constructor of each component will receive a single argument of type Context
  • The first statement inside the constructor will be to register the component with the context object (using the appropriate key)
  • When the constructor needs to initializes a field with another component it will obtain this component from the context object by specifying the need component's key.
  • When the context receives an obtain(key) message it will check if a a component is already registered under this key. If so, it will return it. Otherwise it will invoke the factory object registered under this key.

This pattern is widely used within the implementation of Sun's Javac compiler. You can look at it here (code of the Context class) and here (code of one of components making up the compiler. In particular, take a look at the constructor)

Another alternative is that you move to a Dependency Injection container which will resolve these dependencies for you.

Itay