2

我对此发表了很长的帖子,并决定可以将其总结得更短。规范地说,在类中包含数据成员而不是继承它更好吗?我发现我可以以任何一种方式实现相同的功能,但我真的不知道我应该注意什么警告。

代码示例

#include "KClass.h"
class KPC : public KCharacter {
private:
    KClass MyClass;
};

相对

class KClass : public KCharacter {

};

class KPC : public KClass {


};

在第一个示例中,每当我需要 KClass 数据中的某些内容时,我都可以通过 MyClass-> 访问它

在第二个类中,KPC 类将直接访问它们,因为它将继承数据成员。

对于我的问题的细节,我想我应该详细说明类的功能。

研发形式。每个角色都有一个类别,这将决定:武器/盔甲熟练程度、额外防御、特殊能力,即防御者有标记。

所以对我来说,继承它是有意义的。但是,类是更特定的 PC 还是 PC 是特定种类的类?游戏中有很多 PC 不是特定的类,实际上类应该从概念上继承 PC,它是 PC 的更“专业”形式。那么我想以 KClass: KPC 的方式构造它吗?

起初实现 Has-A 似乎更容易,但现在我第二次猜测它。因此,我在这里问这个问题的原因。

4

4 回答 4

3

这是设计问题以及您要建模的内容。Scott Meyers 的Effective C++将注意到公共继承(第二个示例)模型“is-a”,而组合(第一个示例)模型“is-implemented-in-terms-of”或“has-a”。因此,对于您的示例,您应该决定扮演什么角色KClass以及这些哲学中的哪一个更有意义。只看名字KCharacterKClassKPC,我很难说出它们的目的。

于 2013-07-20T01:03:36.613 回答
3

一般来说,组合优于继承。但这取决于您到底想做什么。大多数情况下认为:

IS A -> inheritance
HAS A -> composition 

当您想要/需要扩展基类时继承。如果您只需要使用另一个类,只需在另一个类中使用它的实例即可。

旁注,组合和聚合基本上是一回事。概念上略有不同,在代码中,同样的事情。

于 2013-07-20T01:05:38.300 回答
2

这真的取决于你想要做什么。是的,两者在机制上都实现了相似的东西,但规则是“是一个”或“有一个”来决定走哪条路。

如果KPC真的是“is-a”形式KClass,那么你应该使用继承。这意味着您正在寻找解决多态问题 - 您有几个相似的项目:

class AeroPlaneBase
{
  ...
};

class JetPlane : public AeroPlaneBase
{
  ...
};

class PropellerPlane : public AeroPlaneBase
{
  ...
}; 

class GliderPlane : public AeroPlaneBase
{
};

所有这些飞机都可以做类似的事情——但它们的行为略有不同,因此它们需要不同的类来描述它们的行为。

现在,每架飞机将有零个或多个“引擎”,因此该类可能与一个类具有“具有”关系PlaneEngine。滑翔机,这是一种无引擎飞机,没有任何引擎,JetPlane可以有 8 个,也许......

同样,在角色扮演游戏中,玩家“is-a” Character(它也是它的基类和不同的派生形式),例如,但与该类Monster“具有”关系。不是 的 类型Weapon,但它有武器。CharacterWeapon

于 2013-07-20T01:11:43.270 回答
1

概念的

类和对象的概念通常用于对“真实”事物进行建模。但是,让我们本末倒置。

继承概念转移到现实世界将(如其他人所说)是一种IS A关系

  • TFT是屏幕
  • 狐狸是一种动物
  • ...

相反,组合通常被认为是HAS A -relation

  • 一台PC有一个CPU
  • 刀有刀片
  • ...

因此,如果您想在面向对象编程中对后者进行建模,请使用组合。如果是前一个概念,请使用继承。

例子

合成 > 继承

例子对我来说总是很自然。因此,我将尝试进一步说明它。(这里没有封装,抱歉。;))

考虑机动车,分别是汽车。往往有一个引擎,它有一个特定的声音。

struct Engine
{
  void sound (void) const { std::cout << "BROOOM" << std::endl; }
  void open_valve (void) { /* ... */ }
};

引擎还可以执行某些特定于引擎的任务。

现在我们可以有两个指定的选项来将引擎包含到汽车中:继承或组合。

struct Car_A : public Engine { }; 

乍一看,这似乎是合适的。我们不需要重新提供,sound()因为汽车(初步近似)听起来就像一个引擎。

Car_A a_car;
a_car.sound(); // mycar sounds like a car!

但是噪音不是很真实:没有胎面噪音,没有空气抽风。所以我们可以只隐藏底层方法并定义:

struct Car_A : public Engine 
{ 
  void sound (void) const 
  {
    std::cout << "tread noise + air draft" << std::endl;
    Engine::sound();
  }
}; 

我们还有一个小问题。

a_car.open_valve(); // ?

阀门的概念是发动机的一部分,但不是汽车的一部分,但我们可以在汽车上使用这种方法。这辆车一个引擎,但它不是一个。我们现在可以切换到私有继承,但该方法仍然存在,尽管不可访问。

使用类型的指针时可以看到另一个(概念较少的)问题:

Engine * pointer_to_engine(new Car_A); // works

一个实际上是汽车的发动机?“(疑似)发动机”展示汽车行为,反之亦然?好吧,这看起来不像在这里做事的方式。

让我们看一下组合:

struct Car_B 
{ 
  void sound (void) const 
  { 
    std::cout << "tread noise + air draft" << std::endl;
    engine.sound(); 
  }
  void open_door (void) { /* ... */ }
  Engine engine;
}; 

事情应该是这样的:一辆汽车一个[n](成员)引擎,听起来像一个引擎并有助于汽车的声音,并且汽车中不存在任何不属于a概念的方法车。

Car_B b_car;
b_car.sound(); // still sounds like a car!
b_car.engine.open_valve(); // meaningful for an engine!

在这里,我们有一个组合优越的案例。

  • 模拟“真实”情况。
  • 所有概念都保持其有效性。(没有意外行为。)

继承 > 组合

现在我们在示例中添加另一个概念:车辆。

struct Wheel {};
struct Motorvehicle
{
  virtual void sound (void) const { engine.sound(); }
  Engine engine;
  std::vector<Wheel> wheels;
};

汽车是由发动机驱动的,所以它知道发出发动机的声音。然而,抽象的车辆不知道它的具体物体会有多少个轮子(摩托车?汽车?)或者它的形状是如何形成的,所以它不能告诉任何关于胎面噪音和气流的信息。

这次我们先看构图(奇迹奇迹……):

struct Car_C
{
  void sound (void) const 
  { 
    std::cout << "tread noise + air draft" << std::endl;
    vehicle.sound();
  }
  Motorvehicle vehicle;
};

看起来合法,不是吗?

Car_C c_car;
c_car.sound(); // correct sound!
c_car.vehicle.sound(); // what the hell is "the vehicle of a car"?
c_car.wheels.... // error the car has no wheels?!

“假装”车轮是汽车的一部分,这将要求我们为汽车添加额外的功能。如果我们改用继承,这种一致性是从头开始的。

struct Car_D 
  : public Motorvehicle
{
  void sound (void) const 
  { 
    std::cout << "tread noise + air draft" << std::endl;
    Motorvehicle::sound();
  }
};

的可观察行为Car_D更像您期望的那样。

Car_D d_car;
d_car.sound(); // correct sound!
d_car.wheels.[...] // valid, our car has wheels!

结论

考虑是使用继承还是组合并不总是像我的示例中那样容易,但您应该尝试权衡并选择在反映所需行为方面表现更好的概念。如果指定的基类描述了派生类的抽象概括,那么这是继承的一个很好的提示。

于 2013-07-20T02:17:51.070 回答