开发者

C#: How to resolve this circular dependency?

开发者 https://www.devze.com 2022-12-27 20:34 出处:网络
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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消