概念的
类和对象的概念通常用于对“真实”事物进行建模。但是,让我们本末倒置。
将继承概念转移到现实世界将(如其他人所说)是一种IS A关系。
相反,组合通常被认为是HAS A -relation。
因此,如果您想在面向对象编程中对后者进行建模,请使用组合。如果是前一个概念,请使用继承。
例子
合成 > 继承
例子对我来说总是很自然。因此,我将尝试进一步说明它。(这里没有封装,抱歉。;))
考虑机动车,分别是汽车。往往有一个引擎,它有一个特定的声音。
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!
结论
考虑是使用继承还是组合并不总是像我的示例中那样容易,但您应该尝试权衡并选择在反映所需行为方面表现更好的概念。如果指定的基类描述了派生类的抽象概括,那么这是继承的一个很好的提示。