Sunday, June 28, 2009

Inversion of Control (IoC)

In our game, a monster wakes up because some players have entered its domain. It's in a dark corner, so it waits for the players to pass by and then lunches them.


Assuming the flow of control starts with the monster object, how does it communicate its intentions to the combat system?


1) The direct approach would be for the monster to create and reference the combat system.



public class Monster : ICreature
{
private DefaultCombatSystem combatSystem =
new DefaultCombatSystem(); // Bad

protected void GetUpToNoGood()
{
//...
ICommand command = new LunchPlayers(players);
IResponse response =
combatSystem.HandleCommand(this, command);
//...
}
//...
}

But this comes with a raft of problems. The least of which is when the combat system changes, the monster will need updating.


2) A better approach is to refer to an interface for combat systems rather than a particular implementation. This will save on the refactoring. The monster can also ask a factory for the combat system that implements this interface. This adds a dependency on a factory however, which is not ideal.


So instead we inject the combat system into the monster. This is known as dependency injection and removes the monster's dependency on a concrete combat system or factory.




public class Monster : ICreature
{
private ICombatSystem combatSystem; // Better

// Constructor injection
public Monster(ICombatSystem combatSystem)
{
this.combatSystem = combatSystem;
}
//...
}

This would be perfect for things a monster does depend on, like its stomach, but the combat system doesn't belong here.


3) An even better approach is to expose an event that the monster fires. This frees the monster from combat system concerns altogether, so it can concentrate on what monsters do best.




public delegate void CommandHandler(ICommand command);

public class Monster : ICreature
{
public event CommandHandler Command; // Best

protected void GetUpToNoGood()
{
//...
ICommand command = new LunchPlayers(players);
Command(this, command);
//...
}
//...
}

Wiring up the event,




public class DefaultCombatSystem : ICombatSystem
{
private IList<ICreature> creatures;

public void AddCreature(ICreature creature)
{
creatures.Add(creature);
creature.Command += OnCommand;
}

public void OnCommand(ICommand command)
{
// Handle the command
}
//...
}

This is the observer design pattern using events.


Control has shifted to the combat system now. It references the monster and will make the required calls to resolve the combat. This is known as inversion of control (IoC) or the Hollywood principle - "Don't call us, we'll call you".


In hindsight it's easy to see we had things backwards from the start, but the direct approach is all too common. It's a hangover from procedural programming where the programme's flow of control acts directly rather than setting up an object model and then handing over control to one of these objects.


In this way, the combat system and the monster are much easier to maintain, test and debug.

No comments:

Post a Comment