继承还是包含? C ++

时间:2013-07-20 01:00:19

标签: c++ class inheritance structure

我有一个非常长的帖子,并认为它可以总结得更短。从典型上讲,将数据成员包含在类中而不是继承它是否更好?我发现我可以用任何一种方式实现相同的功能,但是我不知道应该注意哪些警告。

代码示例

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

class KClass : public KCharacter {

};

class KPC : public KClass {


};

在第一个例子中,只要我需要KClass数据中的某些内容,我就可以通过MyClass->

访问它

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

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

D& D格式。每个角色都有一个等级来确定:武器/护甲的熟练程度,奖励防御,特殊能力,即防御者有标记。

所以对我而言,继承它是有道理的。但是,是一类更具体的PC还是PC的特定类?游戏中有如此多的PC不是特定的类,实际上类应该继承PC的概念意义上它是PC的“专业”形式。那么我想以KClass:KPC的方式构建它吗?

首先实现Has-A似乎更容易,但现在我是第二个猜测它。因此,我在这里,问这个问题。

4 个答案:

答案 0 :(得分:3)

这是设计问题以及您要模拟的内容。 Scott Meyers的 Effective C ++ 会注意到公共继承(第二个例子)模型是'a',而组合(第一个例子)模型'是'或'实现的'有个'。因此,对于您的示例,您应该确定KClass正在扮演什么角色以及哪些角色更有意义。只要查看名称KCharacterKClassKPC,就很难说出他们的目的。

答案 1 :(得分:3)

一般来说,构图优于继承。但这取决于你想要做什么。在大多数情况下,请考虑:

IS A -> inheritance
HAS A -> composition 

在需要/需要扩展基类时继承。如果你只需要使用另一个类,只需要与另一个类有一个实例。

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

答案 2 :(得分:2)

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

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

class AeroPlaneBase
{
  ...
};

class JetPlane : public AeroPlaneBase
{
  ...
};

class PropellerPlane : public AeroPlaneBase
{
  ...
}; 

class GliderPlane : public AeroPlaneBase
{
};

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

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

同样,在角色扮演游戏中,玩家“is-a”Character(也是Monster的基类及其不同的派生形式),比方说,但是“has-a “与Weapon班级的关系。 Character不是Weapon的类型,但它有武器。

答案 3 :(得分:1)

概念

类和对象的概念通常用于模拟“真实”事物。但是我们把车放在马前。

继承概念转移到现实世界(如其他人所说) IS A -relation

  • 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](成员)引擎的汽车,它听起来像一个发动机并且对汽车的声音有贡献,并且没有任何方法存在。汽车不属于汽车概念的一部分。

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

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

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

继承&gt;组合物

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

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!

结论

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