1

我有一点循环依赖问题。它工作正常,但它使代码看起来很难看。这是在蛇游戏的背景下。

我有一个类,Snake,它包含一个 SnakeSegments 向量,并管理它们的交互(例如作为一个单元移动和增长,而不是作为单独的实体)。

当 SnakeSegment 与 Food 对象碰撞时,它会将其 hasEaten 成员翻转为 true。Snake 会定期查询 SnakeSegments,主要是针对该成员。如果任何查询返回正值(即有一个击中食物),则 Snake 将作为一个单元增长(即扩大头部并缩小尾部)。这一切都很好,但我更喜欢基于信号的方法,当 SnakeSegment 碰到食物时,它会向 Snake 类发送警报(信号、中断等),告诉它成长. 这意味着我的 Snake 更新函数中不会有丑陋的代码来检查所有段;我会在我的 Snake 类中有一个 OnEat() 函数。

但是,这会导致循环依赖;Snake 包含一个 SnakeSegments 向量,并且 SnakeSegments 有一个 Snake& 或 Snake* 成员,它告诉他们在吃东西时要提醒谁。在代码中,我基本上只需要预先声明 Snake 类:

class Snake;
class SnakeSegment
{
...
Snake* alertOnEat;
...
};

我的 Snake 课程正常工作

#include "SnakeSegment.hpp"

class Snake
{
...
std::vector segments;
...
void OnEat();
...
};

有没有更好的设计呢?请注意,这不仅仅是这里发生的问题。类似的问题发生在许多领域(例如,GameWorld 包含一个 Snake 成员,并且 Snake 会在 GameWorld 死亡时向它发出警报),因此特定于 Snake 和 SnakeSegment 的解决方案不是我正在寻找的。

4

2 回答 2

4

您当前的设计在大多数情况下都非常好。是的,它创建了一个循环依赖,但它也是最简单和最清晰的方法。

但是,您应该限制接口或基类之间的这些依赖关系,而不是直接限制到派生类。世界蛇依赖就是一个很好的例子。您可能不一定希望World该类知道所有可能的游戏对象类型。但是,您可以做的是从一个公共GameObject类派生所有游戏对象,并使其World相互依赖GameObject

还有更复杂的方法可以避免依赖关系,它们各有优缺点,它们在分离度和清晰度方面大多不同。其中,函数指针、委托、观察者、侦听器、函子等。

最后,它总是归结为您准备在架构中引入多少复杂性,以换取灵活性和清晰的关注点分离。

永远不要忘记设计就是妥协

于 2010-09-19T18:05:36.327 回答
3

对于这个问题,您可能想要查看的是 Observer/Observable 设计模式。它允许您创建观察(Snake)可观察对象(SnakeSegment)的对象,并在它们的状态发生变化时立即得到通知。

维基百科有一个很好的例子,它用许多语言编写,包括 C++。

这是许多 GUI 开发中使用的一种常见模式,因此当任何底层模型数据发生更改时,视图层可以更改。

于 2010-09-19T17:33:31.953 回答