请原谅我的小说。我正在制作一个游戏,其中几个角色彼此之间有关系,他们需要能够相互交互并存储一些关于他们彼此感觉的关系数据。
我为每个角色都有一个对象。为了执行这些交互,每个角色对象是否都有一个包含所有其他角色对象的数组?有更好的方法吗?
编辑回答问题: 我的语言是C#。
我在想的是,setup类会创建一个包含所有字符的数组,然后将该数组作为参数传递给每个字符的构造函数。
关于关系类型,有五个字符,但我想在以后添加更多字符时使其可扩展。角色互相交谈。他们说的是他们的情绪和他们对这个角色的感受。另外,另一个角色对他们说的话会影响他们的情绪以及他们对角色的感受。他们可以进行一对一的对话或分组交谈,因此角色A可以同时对B,C,D,E和F字符进行说明。
顺便说一句,我从这个帖子中学到了很多东西。非常感谢大家!
答案 0 :(得分:16)
对象保留彼此引用的做法并不坏 - 树图中的节点保持对其子节点的引用并且子节点保持对其父节点的引用并不罕见。
可能成为问题的一件事是你描述的每个角色保持对所有其他角色的引用。这是一个NxN矩阵,随着字符数的增加,它会爆炸你的内存使用。
在每个字符对象中存储所有字符对象列表的替代方法可能是设置关键列表或字典,其中键是(字符1,字符2),内容是关系。此列表可以是全局的,并存储所有字符之间的所有关系。这里的优点是它与实际关系的数量呈线性增长,而不是与系统中的字符数呈指数关系。
答案 1 :(得分:10)
通常很好地避免对象之间的不必要的引用,但确定不必要的内容是非常主观的并且取决于问题。
如果每个字符都有唯一标识符(例如,唯一名称),则只能为每个字符存储字符标识符的集合和关于该字符的“感觉”。使用其他一些CharacterManager类型,您可以根据标识符查找字符。
另一种选择(如果意见只是游戏的一个小方面可能更可取)是拥有一个单独的“RelationshipManager”类,它可以回答诸如“字符Y对字符Y的感觉如何”这样的查询?这可能是一个更好的设计,因为该责任与您的核心角色分开管理。该经理可以直接引用字符,也可以使用标识符。
答案 2 :(得分:6)
在面向对象设计中,这类问题的常见方法是Mediator Pattern
您描述的方法存在的问题是如何更新关系?例如,如果从游戏中删除了角色A,那么如何更新其他角色以从其关系中删除角色A?
我建议使用另一个管理关系的Object。然后,该对象可以负责更新关系。使用相同的示例,当删除字符A时,关系管理器简单地删除涉及该字符的所有关系。
答案 3 :(得分:3)
不,这根本不是一个坏习惯。显然你可以这样做,以使其难以维护或理解,但没有任何固有的错误与对象之间的引用。
虽然我会推荐你的语言提供的任何集合类型而不是数组,但你的建议并没有错。
答案 4 :(得分:3)
如果这种关系持久,那么这样做是有意义的。我不知道你的游戏是如何运作的,但似乎所有角色都不可能在任何时间点互相交流。在更临时的情况下,您可能希望实施某种“访问”,游戏领导者会向参加聚会的人提供参考。
你也可以选择通过界面的使用来揭示不同级别的细节:如果一个食尸鬼遇到一个巫师,他可以得到一个人类参考向导,因为食尸鬼不会以不同的方式对待不同类型的人类。通过使用“需要知道基础”,可以减少代码中的耦合量,并且可以更好地重用代码:如果Ghoul访问农民,则无需编写任何新代码。
答案 5 :(得分:3)
我为每个角色都有一个对象。 对每个角色都不好 对象有一个所有的数组 其他角色对象以便 进行这些互动?有没有 更好的方法吗?
这实际上取决于角色之间的相互作用,它们分为两类:
1)动作 - A改变B上的某个状态
在这种情况下,你可能会写一些类似的东西:
class Player {
Player[] players;
...
public void ExchangeItem(Item item, string playerName) {
Player target = players.First(x => x.Name = playerName);
target.Items.Add(item);
this.Items.Remove(item);
}
}
2)反应 - B在A
中侦听状态的变化你不会写:
public class Player {
Player[] players;
public void Die() {
foreach(Player p in players)
p.Mourn();
}
}
它关于单一责任。对象应该只知道并关心自己的状态和行为,他们不应该了解其他人。在这种情况下,Die方法似乎对其他玩家处理它的方式了解得太多。
相反,您可以将其重新编写为事件:
class Player {
public event EventHandler PlayerDied;
public void Die() {
if (PlayerDied != null)
PlayerDied(this, EventArgs.Empty);
}
}
其他对象可以挂钩到PlayerDied事件并以他们想要的方式做出反应。
答案 6 :(得分:3)
我可以看到这是一篇旧文章,但我可以看到有些人说双向情侣没有任何错误(即两个对象相互引用)。 双向耦合,如循环引用确实存在问题,如果您可以设计软件以避免使用它们,则会使代码更易于维护并且不太可能失败。
另外,高度耦合的解耦软件需要花费很多精力,所以如果需要这种类型的耦合,最好让它短暂存在。
现在,下面的C ++示例看起来有点疯狂,并且是一个双向耦合的代码片段,它将在析构函数中失败,但实际上这种类型的问题确实存在于具有数百万行代码的遗留代码中,其中跟踪指针和参考文献的生命周期变得越来越困难。
class B;
class A
{
public:
A(B* b = nullptr)
: m_b(b)
{
}
~A()
{
delete m_b;
}
void setB(B* b)
{
delete m_b;
m_b = b;
}
private:
B* m_b;
};
class B
{
public:
B(A* a = nullptr)
: m_a(a)
{
}
~B()
{
delete m_a;
}
void setA(A* a)
{
delete m_a;
m_a = a;
}
private:
A* m_a;
};
int main()
{
A a(new B());
B b;
b.setA(&a);
}
当处理垃圾收集语言时,这些成为引用,并且当存在一个引用时,高度耦合系统中的对象将存活很长时间,这可能导致过多的内存使用,文件句柄长时间保持打开状态,还有很多其他类型的问题。
让两个对象相互引用是不好的做法? 是或否,取决于您要解决的问题,但如果您能找到一种方法在没有耦合的情况下明智地解决问题,那么它将为您节省更多的痛苦。
还要记住,对于每个类耦合,这意味着更改一个类的机会可能会破坏另一个类的功能。
一些大学现在也在教授一些东西,称为: 高内聚和低耦合
对于您的角色示例,它会在您的软件发展过程中涉及处理情况,例如在添加或删除人员时保持多个列表是最新的,通知在其列表中有相同人员的任何人任何变化等 其中一个回复单独管理它们的建议并不是一个坏主意,例如,你可以有一个Person和People类,在People上有一个界面,如People.chat(person1,person2)等等。 这将取决于您的设计以及您想要实现的目标。
这个话题引发了很多辩论,有时会产生很高的声音,但我只关注可重复使用,易于测试(单元测试,组件测试等),并且难以打破策略(It& #39;对SO的评论太过分了。) 这是每个人都是专家的主题,没有人能够达成一致,所以你的问题确实是主观的,经验会给你你所寻求的答案。
答案 7 :(得分:2)
我注意到面向对象的设计,如果对于没有编码的人来说听起来合乎逻辑,那么你可能很好。
说你有一辆出租车......出租车应该知道它的工作对象是什么?
或者孩子应该知道其父母是谁?
显然要保持理智。
答案 8 :(得分:2)
一种方法,当你有十几个不同的对象都相互引用时,就是把一些东西放在中间 - 一个介体 - 它包含所有的引用。然后每个人都通过调解员引用其他角色。您将引用计数从n ^ 2减少到2n - 每个人都知道中介,并且它知道所有人。
答案 9 :(得分:1)
您可以考虑的另一个选择是信息中心的想法。您有一些中心对象,其工作是将消息分发给其他对象。其他对象通过注册通知对象表达他们对接收消息的兴趣。这种方法的好处是可以将对象彼此分离,还可以避免与循环引用保留相关的内存问题。如果您发送一吨消息,则一个缺点可能是通知对象的瓶颈。
答案 10 :(得分:0)
不一定,但这取决于您如何定义和引用关系。例如,如果要将一个角色对象替换为另一个角色,会发生什么?您是否必须更新引用该对象的所有对象?
答案 11 :(得分:0)
您描述的问题可以自然地建模为图形,其中节点是您的游戏角色,它们之间的关系是图形的边缘。根据游戏角色之间关系的性质,边缘可以是定向的或不定向的。你可以考虑使用像Neo4j这样的图形数据库,如果它适合你的问题。
就保留参考文献而言,我认为重要的是要记住,在设计中尽可能采用松耦合的设计原则。
答案 12 :(得分:-3)
我为每个角色都有一个对象。 对每个角色都不好 对象有一个所有的数组 其他角色对象以便 进行这些互动?有没有 更好的方法吗?
一个词中的接口就是这个的反击。否则,你最终会在一段时间内更新所有引用。