如何处理类层次结构

Posted

技术标签:

【中文标题】如何处理类层次结构【英文标题】:How to handle class hierarchy 【发布时间】:2011-10-17 22:20:42 【问题描述】:

我有一个关于如何处理这种类层次结构的问题。层次结构与游戏有关,但我认为它足够笼统,可以通过 gamdev 来讨论。我想这种事情偶尔会出现,所以我很好奇可能的解决方案

我有一个名为 BaseCharacter 的基类,它在我的游戏中定义了一个角色。它包含所有基本统计数据、状态效果持有者、事件等,以具有基线特征。我的大部分游戏逻辑都在 BaseCharacter 的实体上运行,并在必要时进行强制转换。

从那里我继承了一个 Humanoid 类,它添加了必要的结构和方法来为角色添加设备(武器、盔甲等),最后我有一个 PlayableCharacter 类,它继承自 humanoid 并添加了诸如当前 EXP 之类的东西,可配置的能力结构等。

所以类结构很简单:

BaseCharacter--->Humanoid--->PlayableCharacter

这种结构非常适合定义玩家方面的角色,尽管我可能会决定进一步改进结构。我必须能够将“敌人”类添加到层次结构中。为了满足对敌人的要求,我需要添加一些与经验相关的数据结构/方法/击败敌人的点数,战利品表等。

问题是,我在哪里添加这些结构?将它们添加到 BaseCharacter 似乎是个坏主意,因为我基本上是在说每个角色都具有敌方实体所需的方法/数据结构。我考虑过启动一个单独的类流,最终看起来像这样:

BaseCharacter--->Humanoid--->PlayableCharacter
           \---->Enemy--->EnemyHumanoid

Enemy 继承自 BaseCharacter。 EnemyHumanoid 将添加与从 BaseCharacter->Humanoid 继承的相同的方法/数据结构集。这可行,但意味着我不能将 EnemyHumanoid 转换为 Humanoid,尽管 EnemyHumanoid 满足 Humanoid 的要求。这也增加了冗余,因为我会让这两个继承添加完全相同的对象/方法集。

然后考虑使用一个名为 EnemyInterface 的单独类,并最终得到这样的继承层次结构:

BaseCharacter----->Humanoid----->PlayableCharacter
    |                 |                
    V                 V                
  Enemy         EnemyHumanoid   

Enemy 和 EnemyHumanoid 都实现了 EnemyInterface。但是,我现在失去了 EnemyEnemyHumanoid 关系。我应该在这里使用多个 inehritance 吗? (是否有 EnemyHumanoid 从 Enemy 和 Humanoid 继承?)这被认为是不好的形式吗?有没有更合乎逻辑的方法来做到这一点?这里的例子很简单,但如果我最终将我的类层次结构拆分得更多,它只会变得更复杂。

任何帮助将不胜感激

【问题讨论】:

【参考方案1】:

听起来您正在使用继承来重用,而不是遵循 LSP (http://en.wikipedia.org/wiki/Liskov_substitution_principle),这是通往地狱的道路。

我的一般建议是仅对 is-a 关系使用继承。也就是说,您可以将一种类型替换为另一种类型。对于大多数解决方案,我更喜欢组合而不是继承,因为我发现它更简单。

在您的具体情况下,为什么认为 Enemy 与 PlayableCharacter 不同?考虑将 EXP 和 Loot Tables 放在类层次结构之外,您可以在其中将 Humanoid 传递给它并分别计算这些项目。

C++ 是一种多范式语言。您不必局限于 OO 解决方案。

祝你好运!

【讨论】:

【参考方案2】:

这个可能的怎么样?

  BaseCharacter----->Humanoid----->PlayableCharacter
                      / \                
                     /   \                
                Enemy     EnemyHumanoid   

EnemyHumanoid 可能只需要 Humanoid 的一些功能。

【讨论】:

【参考方案3】:

要考虑的问题是Humanoid 实例是否需要转换为BaseCharacter 实例。你可能会发现它实际上没有。你真的需要遍历所有 Humanoid 角色的集合吗?

如果是这样定义:

Humanoid

BaseCharacter->PlayableCharacter (contains Humanoid)
            \---->Enemy---->EnemyHumanoid (contains Humanoid)

[您可能需要考虑将 Enemy 抽象为定义一个具体的 EnemyNonhumanoid。]

另一种方法是将 Humanoid 定义为接口,并在 PlayableCharacter 和 EnemyHumanoid 上提供 getHumanoid() 方法,在每种情况下都实现为句柄。

【讨论】:

【参考方案4】:

我建议您对基于组件的架构进行一些研究,而不是重新排列您的类层次结构来解决这个特定问题(请参阅 StackUnderblow 的回答以了解如何做到这一点)。基本上,与其拥有一个描述使 PlayableCharacter 独一无二的事物的类,不如将一个组件(或一组组件)移植到某种基础容器上。

这种方法更加灵活,并且随着设计的发展更容易重构。例如,如果您有一些敌人和人类都应该能够访问的功能,那么您将不得不将系统挂钩添加到您的基类中,从而为无法使用该功能的敌人引入开销。描述一个简单的问题有很多词,但随着游戏的发展,您会一遍又一遍地遇到问题。

Here 是一个很好的 SO 讨论,扩展了我的建议。

Bheeshmar 是对的,你正在通往地狱的道路上!

【讨论】:

【参考方案5】:

您正在尝试根据多个方面对您的 BaseCharacter 类进行子类化(错误,我不确定“方面”在这里是正确的词,我不是以英语为母语的人,抱歉),即:

形态 - 人形/动物/等。 对齐 - 敌人/朋友/等等。

正如您所观察到的,这总是会导致问题:迟早您会想要创建应该从 BaseCharacter 的多个子类(通常是每个方面类别中的一个)继承的类,例如。人形+敌人。在这种情况下,要么使用多重继承(由于实现问题,在 C++ 中不推荐),要么需要重新设计代码。

通常最好的解决方案是根据前面确定的方面拆分您的对象。例如。在 BaseCharacter 对象中,您可以有两个指针,一个 CharacterForm* 和一个 CharacterAlignment*。然后可以独立地继承 CharacterForm(如 HumanoidForm、AnimalForm 等)和 CharacterAlignment(EnemyAlignment、FriendAlignment 等),并根据需要组合它们。

请注意,正如 Dan O 所建议的那样,这种方法确实是迈向基于组件的设计的第一步。

每次你都需要做类似的事情,你会发现自己创建了一个子类,它是其基础的子类,其意义与以前的子类不同(例如,在创建 Humanoid 和 Animal 之后,你需要 Enemy - > 这显然是对 BaseCharacters 进行分类的另一种方式)。经验法则是每个类都只能是根据 one 方面的子类。

【讨论】:

【参考方案6】:

这个怎么样?

BaseCharacter

IBehaviour是接口

HumanoidBehaviourIBehaviour 的实现

BaseCharacter 包含IBehaviour

Enemy 继承 BaseCharacter 并包含 HumanoidBehaviour

PlayableCharacter 继承 BaseCharacter 并包含 HumanoidBehaviour

所以逻辑上我们会得到:Enemy is-a BaseCharacterhas-a 行为 HumanoidBehaviour

Strategy design pattern 可能会在这里有所帮助 还有funny book 有一个很好的例子)

【讨论】:

以上是关于如何处理类层次结构的主要内容,如果未能解决你的问题,请参考以下文章

深度学习分类中如何处理类之间的相关性?

在 Swift 中实现数据存储的最佳方法是啥,如何处理类更改

如何使用 ServerManager 从类库中读取 IIS 站点,而不是 IIS express,或者提升的进程如何处理类库?

我应该如何处理 RESTful API 中的对象层次结构?

我如何处理导航返回按钮视图层次结构?

SSAS - 如何处理具有有效重复项的维度?