请求简单的 C++ 组合与继承示例

A Request for Simple C++ Composition vs. Inheritance Examples

我想了解 C++ 中组合和继承之间的语法差异。

我希望有人能提供两个简单的例子。一个使用组合的 class 示例和一个使用继承的 class 示例。

当然可以,为什么不呢?既然我喜欢机器人,那我们就做一个能走动能抓东西的机器人吧。我们将使用继承制作一个机器人,使用组合制作另一个机器人:

class Legs
{
public:
   void WalkAround() {... code for walking around goes here...}
};

class Arms
{
public:
   void GrabThings() {... code for grabbing things goes here...}
};

class InheritanceRobot : public Legs, public Arms
{
public:
   // WalkAround() and GrabThings() methods are implicitly
   // defined for this class since it inherited those
   // methods from its two superclasses
};

class CompositionRobot
{
public:
   void WalkAround() {legs.WalkAround();}
   void GrabThings() {arms.GrabThings();}

private:
   Legs legs;
   Arms arms;
};

请注意,至少对于此示例,CompositionRobot 通常被认为是更好的方法,因为继承意味着 is-a 关系,而机器人不是特定类型的 Arms 并且机器人不是特定类型的 Legs(而是机器人 has-armshas-legs)。

为了扩展@jeremy-friesner 的答案(并且主要重用他的代码),很多时候组合是使用比这更多的 classes 来实现的。本质上,Legs 和 Arms classes 将是接口的实现。这使得注入这些依赖项变得容易,因此,在对复合对象进行单元测试时 mock/stub 将它们移除。然后你会得到类似的东西(忽略虚拟析构函数......):

class Walker // interface
{
public:
    virtual void Walk() = 0;
}

class Legs : public Walker
{
public:
    void Walk() {... code for walking around goes here...}
}

class Grabber // Interface
{
public:
    virtual void GrabThings() = 0;
}

class Arms : public Grabber
{
public:
    void GrabThings() {... code for grabbing things goes here...}
}

class InheritanceRobot : public Legs, public Arms
{
public:
    // Walk() and GrabThings() methods are implicitly
    // defined for this class since it inherited those
    // methods from its two superclasses
};

class CompositionRobot
{
public:
    CompositionRobot(Walker& walker, Grabber& grabber) 
        : legs(walker), 
          arms(grabber) 
    {} 
    void Walk() {legs.Walk();}
    void GrabThings() {arms.GrabThings();}

private:
    Walker& legs;
    Grabber& arms;
};

所以用于腿和手臂的实际实现可以设置在 运行 时间而不是编译时间。

顺便说一句,我只是把它写成一个答案,而不是对 Jeremy 的答案的评论,以便从代码格式中受益,所以如果你想投票,请也投票给 Jeremy。

HTH

2021 年 9 月 14 日更新:

我在这个答案中注意到的一件事是我将组合和聚合混为一谈。在组合中,如果父对象不复存在,那么子对象也不复存在,而在聚合中,子对象可能在父对象被销毁后存在。在我给出的描述中,在 CompositionRobot 构造函数中传递对子对象实例的引用意味着聚合关系而不是组合。但是,如果您在定义参数和创建对象时使用 std::unique_ptr(),并且在将它们存储在 CompositionRobot 的构造函数中时使用 std::move(),则效果与在 Jeremy 的回答中,对象(而不是指针或对它们的引用)被定义为 class 成员。