代码之家  ›  专栏  ›  技术社区  ›  LuminousNutria

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

  •  12
  • LuminousNutria  · 技术社区  · 7 年前

    我试图理解C++中组合和继承之间的语法差异。

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

    2 回复  |  直到 6 年前
        1
  •  25
  •   Jeremy Friesner    5 年前

    当然,为什么不呢?既然我喜欢机器人,让我们制作一个可以四处走动和抓取东西的机器人。我们将使用继承制作一个机器人,使用合成制作另一个机器人:

    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-arms has-legs )。

        2
  •  12
  •   cosimo193    3 年前

    要稍微扩展一下JeremyFriesner的答案(并且主要重用他的代码),很多时候合成是使用更多的类来实现的。Legs和Arms类本质上是接口的实现。这使得注入这些依赖关系变得很容易,因此,在对复合对象进行单元测试时,可以模拟/剔除它们。然后您会得到类似的结果(忽略虚拟析构函数…):

    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\u ptr(),在将对象存储在CompositionRobot的构造函数中时使用std::move(),其效果将与Jeremy的回答中的效果非常相似,在Jeremy的回答中,对象(而不是指针或对它们的引用)被定义为类成员。