如何处理类层次结构

时间:2011-08-02 03:35:27

标签: c++ inheritance

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

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

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

所以类结构很简单:

BaseCharacter --->人型生物---> PlayableCharacter

这个结构非常适合定义玩家角色,但我可能会决定再改进一下这个结构。我必须能够在层次结构中添加“敌人”类。为了满足敌人的要求,我需要添加一些与EXP /点相关的数据结构/方法来击败敌人,战利品表等。

问题是,我在哪里添加这些结构?将它们添加到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。但是,我现在失去了Enemy< - > EnemyHumanoid的关系。我应该在这里使用多个不合格吗? (来自Enemy和Humanoid的EnemyHumanoid inheirt?)被认为是不好的形式?有更合理的方法吗?这里的例子相当简单,但如果我最终更多地分割我的类层次结构,它只会变得更复杂。

任何帮助将不胜感激

6 个答案:

答案 0 :(得分:2)

我建议您对基于组件的体系结构进行一些研究,而不是重新安排您的类层次结构以解决此特定问题(请参阅StackUnderblow的答案以了解如何执行此操作)。基本上,不是让一个类描述使PlayableCharacter独特的东西,而是将一个组件(或一组组件)移植到某种基本容器上。

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

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

Bheeshmar是对的,你正走上地狱之路!

答案 1 :(得分:1)

听起来你正在使用继承来重用而不是遵守LSP(http://en.wikipedia.org/wiki/Liskov_substitution_principle),这是地狱之路。

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

在您的具体情况下,为什么考虑Enemy与PlayableCharacter不同?考虑在类heirarchy之外设置EXP和Loot Tables,你可以在其中传递Humanoid并分别计算这些项目。

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

祝你好运!

答案 2 :(得分:1)

这个可能的怎么样?

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

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

答案 3 :(得分:1)

你试图根据多个方面对你的BaseCharacter类进行子类化(错误,我不确定“方面”这里是正确的词,我不是英语母语人士,对不起),即:

  • 表格 - 人形/动物/等
  • 阵营 - 敌人/朋友/等

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

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

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

你需要做类似的每次你发现自己创建一个子类,它的基类是与前一个子类不同的意义上的子类(例如,在创建Humanoid和Animal之后你需要敌人 - >这显然是对BaseCharacters进行分类的另一种方式。经验法则是每个类只应该是一个方面的子类。

答案 4 :(得分:0)

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

如果是这样定义:

Humanoid

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

[你可能想考虑让Enemy摘要定义一个具体的EnemyNonhumanoid。]

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

答案 5 :(得分:0)

这个怎么样?

BaseCharacter

IBehaviour是界面

HumanoidBehaviourIBehaviour

的实施

BaseCharacter包含IBehaviour

Enemy继承BaseCharacter并包含HumanoidBehaviour

PlayableCharacter继承BaseCharacter并包含HumanoidBehaviour

从逻辑上讲,我们会得到:Enemy is-a BaseCharacter有一个行为HumanoidBehaviour

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