继承还是包含?C++
Inherit or include? C++
我有一篇很长的文章,决定可以总结得更短。 从规范上讲,在类中包含数据成员比继承它更好吗? 我发现无论哪种方式,我都可以实现相同的功能,但真的不知道我应该注意哪些注意事项。
代码示例
#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 似乎更容易,但现在我再次猜测它。 因此,我在这里问这个问题的原因。
一般来说,组合比继承更好。但这取决于你到底想做什么。在大多数情况下,认为:
IS A -> inheritance
HAS A -> composition
在需要/需要扩展基类时继承。如果您只需要使用另一个类,只需将它与另一个类一起实例即可。
旁注,组合和聚合基本上是一回事。在概念上略有不同,在代码中,同一件事。
这是一个设计和你试图建模的问题。 斯科特·迈耶斯(Scott Meyers(的有效C++将注意到公共继承(第二个示例(模型"is-a",而组合(第一个例子(模型"是按术语实现"或"has-a"。 所以,对于你的例子,你应该决定KClass
扮演什么角色,以及这些哲学中的哪一个更有意义。 光看名字KCharacter
、KClass
和KPC
,我很难说出它们的目的。
这真的取决于你想做什么。是的,两者都实现了机械上相似的事情,但规则是"is-a"或"has-a"来决定走哪条路。
如果KPC
真的是"一种"形式的KClass
,那么你应该使用继承。这意味着您正在寻找解决多态问题 - 您有几个相似的项目:
class AeroPlaneBase
{
...
};
class JetPlane : public AeroPlaneBase
{
...
};
class PropellerPlane : public AeroPlaneBase
{
...
};
class GliderPlane : public AeroPlaneBase
{
};
所有这些飞机都可以做类似的事情 - 但它们的行为略有不同,因此它们需要不同的类别来描述它们的行为。
现在,每架飞机将有零个或多个"引擎",因此该类可能与PlaneEngine
类具有"has-a"关系。滑翔机是一架无引擎飞机,没有任何引擎,JetPlane
可以有 8 个,也许......
同样,在角色扮演游戏中,玩家"是"Character
(这也是Monster
的基类及其不同的派生形式(,但与Weapon
类"有"关系。Character
不是一种Weapon
,但它有武器。
概念
类和对象的概念通常用于对"真实"事物进行建模。但是让我们本末倒置。
将继承概念转移到现实世界将是(正如其他人所说的(是 IS A 关系。
- TFT 是屏幕
- 狐狸是动物
- 。
相比之下,该组合通常被认为是HAS A关系。
- 一台电脑有一个中央处理器
- 刀有刀片
- 。
因此,如果您想在面向对象编程中对后者进行建模,请使用组合。在前一个概念的情况下,使用继承。
例子
组合>继承
例子对我来说总是很自然。因此,我将尝试进一步说明它。(这里没有封装,对不起,;)(
分别考虑汽车。往往有一个引擎,它有一个特定的声音。
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!
在这里,我们有一个构图优越的情况。
- 对"真实"情况进行了建模。
- 所有概念都保持其有效性。(无意外行为。
组合>
继承现在我们在示例中添加了另一个概念:车辆。
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!
结论
考虑是否使用继承或组合并不总是像我的例子那样容易,但你应该尝试权衡并选择在反映所需行为方面表现更好的概念。如果指定的基类描述了派生类的抽象泛化,则这是继承的良好提示。
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从包含m行的文件中提取n行,必要时(惰性地)重复该文件
- 编译包含字符串的代码时遇到问题
- c++库的公共头文件中应该包含什么
- 将包含C样式数组的对象初始化为成员变量(C++)
- 是否需要删除包含对象的"pair"?
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 如何将包含epoch时间的十六进制字符串转换为time_t
- 使用mongocxx驱动程序时包含头文件问题
- 如何在h文件中包含.o对象文件
- 在混合代码库中将C转换为C++时出现许多包含错误
- VS2017,C++包含目录与附加包含目录,子文件夹包含失败-但为什么
- cmath抛出错误C2062、C2059、C2143和C2447.cmath包含在矢量文件中
- 为什么您需要C++头文件的包含保护
- 无法在UE4中包含BP类到CPP类
- g++ 说函数不存在,即使包含正确的标头
- 在C++代码中包含opencv时,使用ctypes创建.so文件
- Visual C++GC接口如何启用它以及要包含哪个库
- 当调用switch语句中的函数时(即使函数不包含循环),似乎是永不结束的循环的问题
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)