I have a circular dependency in my code, and I'm not sure how to resolve it.
I am developing a game. A NPC has three components, responsible for thinking, sensing, and acting. These components need access to the NPC controller to get access to its model, but the controller needs these components to do anything. Thus, both take each other as arguments in their constructors.
ISenseNPC sense = new DefaultSenseNPC(controller, worldQueryEngine);
IThinkNPC think = new DefaultThinkNPC(sense);
IActNPC act = new DefaultActNPC(combatEngine, sense, controller);
controller = new ControllerNPC(act, think);
(The above example has the parameter simplified a bit.)
Without act
and think
, controller
can't do anything, so I don't want to allow it to be initialized without them. The reverse is basically true as well. What should I do?
ControllerNPC
using think
and act
to update its state in the world:
public class ControllerNPC {
// ...
public override void Update(long tick)
{
// ...
act.UpdateFromBehavior(CurrentBehavior, tick);
CurrentBehavior = think.TransitionState(CurrentBehavior, tick);
}
// ...
}
DefaultSenseNPC
using controller
to determine if it's colliding with anything:
public class DefaultSenseNPC {
// ...
public bool IsCollidingWithTarget()
{
return worldQuery.IsColliding(controller, model.Target);
}
// ...
}
Separate the model of the controller from the concrete controllerService using an interface.
This is about project references in domain driven design, I wrote a small blog about this problem some time earlier:
http://www.mellekoning.nl/index.php/2010/03/11/project-references-in-ddd/
Use two-phase construction, whereby the objects are constructed with null
references to their related objects, and you then call set
methods to set the references:
ISenseNPC sense = new DefaultSenseNPC(worldQueryEngine);
IThinkNPC think = new DefaultThinkNPC();
IActNPC act = new DefaultActNPC(combatEngine);
controller = new ControllerNPC();
sense.setController(controller);
think.setSense(sense);
act.setSense(sense);
act.setController(controller);
controller.setAct(act);
controller.setThink(think);
// And now the objects are ready to use.
Would it be possible to use events for some of the communication between objects?
From my understanding, the 1st and the main thing is: Controller should not know about thinking, sensing, acting...
I see you have something like 'Update' method for controller and (I guess) controller need to do something depending to current 'thinking','sensing','acting'.
For such case I would add 3 more components on a model level: 'ThinkModel', 'ActModel', 'SenseModel'. They should represent state of corresponding process and know nothing about other world.
Your controller should receive this information from components (Thinking, Acting, Sensing) by methods like 'DoAction', 'ThinkingAbout', 'FeelingSomething' and store it inside.
In the same time it should have a set of events like 'ActionOccured', 'ThinkingOccured', 'SenseingOccured' (last can be phrased like 'FeeledSomething'). These events should be:
- fired in case of any state changed;
- provide corresponding object model;
- should be listened by components.
As a result you will have controller to know about models only, and each component to refer to all models AND controller. Components need to know nothing about each other. Controller need to know nothing about components. And you will be able to create your object in the way like this:
IThinkModel modelThinkg = new ThinkModel();
IActModel modelAct = new ActModel();
ISenseModel modelSense = new SenseModel();
IController controller = new Controller(modelThinkg, modelAct, modelSense);
ISenseNPC sense = new DefaultSenseNPC(controller);
IThinkNPC think = new DefaultThinkNPC(sense);
IActNPC act = new DefaultActNPC(combatEngine, sense, controller);
Constructor of each component can look like this:
class DefaultSenseNPC
{
DefaultSenseNPC(IController controller)
{
_controller = controller;
_contoller.ThinkingAbout += ContollerReceivedNewThinking;
}
private ContollerReceivedNewThinking(IModelThinking modelNewThink)
{
_modelNewThink = modelNewThink;// store it for further calculations.
}
}
Hope this helps.
P.S. In some way, suggested 'architecture' seems similar to MVP patter used in applications with user interface.
精彩评论