C#: 如何解决这个循环依赖问题?

3

我在我的代码中遇到了循环依赖,不确定该如何解决。

我正在开发一款游戏。一个NPC有三个组件,分别负责思考、感知和行动。这些组件需要访问NPC控制器以获取其模型,但控制器需要这些组件才能执行任何操作。因此,它们在构造函数中都将彼此作为参数传入。

ISenseNPC sense = new DefaultSenseNPC(controller, worldQueryEngine);
IThinkNPC think = new DefaultThinkNPC(sense);
IActNPC act = new DefaultActNPC(combatEngine, sense, controller);
controller = new ControllerNPC(act, think);

(上述示例简化了参数。)

没有actthinkcontroller无法执行任何操作,因此我不希望在没有它们的情况下初始化它。反之亦然。我该怎么办?

ControllerNPC使用thinkact来更新其在世界中的状态:

public class ControllerNPC {
   // ...
           public override void Update(long tick)
        {
            // ...
            act.UpdateFromBehavior(CurrentBehavior, tick);

            CurrentBehavior = think.TransitionState(CurrentBehavior, tick);
        }
   // ...

}

DefaultSenseNPC 使用 controller 来确定它是否与任何物体发生碰撞:

 public class DefaultSenseNPC {
       // ...
            public bool IsCollidingWithTarget()
            {
                return worldQuery.IsColliding(controller, model.Target);
            }
       // ...
    }

你开发了自己的解决方案吗? - Budda
4个回答

4

0
从我的理解来看,第一件重要的事情是:控制器不应该知道关于思考、感知和行动的事情。
我看到你有一个像"更新"方法的东西,控制器可能需要根据当前的"思考"、"感知"、"行动"做一些事情。
对于这种情况,我会在模型层上添加三个组件:"思考模型"、"行动模型"、"感知模型"。它们应该代表相应过程的状态,并且对其他领域一无所知。
你的控制器应该通过"执行操作"、"思考某事"、"感受某事"等方法从组件(思考、行动、感知)中接收这些信息,并将其存储起来。
同时,它还应该拥有一组事件,如"发生行动"、"发生思考"、"发生感知"(最后一个可以表达为"感受到某事")。这些事件应该满足以下条件:
- 在任何状态改变时触发; - 提供相应的对象模型; - 被组件监听。
结果就是,你的控制器只需要了解模型,每个组件都需要引用所有模型和控制器。组件之间不需要互相了解,控制器也不需要了解组件。你将能够以这种方式创建你的对象。
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);

每个组件的构造函数可以如下所示:

class DefaultSenseNPC
{
    DefaultSenseNPC(IController controller)
    {
        _controller = controller;
        _contoller.ThinkingAbout += ContollerReceivedNewThinking;
    }

    private ContollerReceivedNewThinking(IModelThinking modelNewThink)
    {
        _modelNewThink = modelNewThink;// store it for further calculations.
    }
}

希望这能有所帮助。
另外,某种程度上,建议的“架构”似乎类似于在带有用户界面的应用程序中使用的MVP模式。

0

使用两阶段构造法,即对象在构造时使用null引用来引用它们的相关对象,然后调用set方法来设置这些引用:

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.

我该如何确保这一点呢?这难道不会使代码变得不稳定,因为另一个程序员可能会忘记第二阶段是必要的吗? - Nick Heiner
此外,我宁愿不让 Controller.ActController.Think 在初始化时间之外被设置。 - Nick Heiner
@Rosarch:只要这个程序员试图让他错误构造的对象做任何事情,他就会发现自己的错误。为了彻底,你可以让这些东西在你要求它们执行操作时断言相关的“set”方法已被调用,并且可以告诉你^H^H^H^H他出了什么问题。 - RichieHindle

0

在对象之间的某些通信中,是否可以使用事件?


你认为这会是什么样子? - Nick Heiner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接