继承与聚合

关于如何在面向对象的系统中最好地扩展,增强和重用代码有两种思想流派:

  • 继承:通过创建子类来扩展类的功能。 覆盖子类中的超类成员以提供新功能。 当超类想要一个特定的接口但不知道它的实现时,使方法抽象/虚拟来强制子类“填空”。

  • 聚合:通过采用其他类并将它们组合成一个新类来创建新的功能。 将一个通用接口添加到这个新类中,以实现与其他代码的互操作性。

  • 每种方式的好处,成本和后果是什么? 还有其他的选择吗?

    我看到这个辩论经常出现,但我认为它没有被问到Stack Overflow(尽管有一些相关的讨论)。 还有一个惊人的缺乏良好的Google结果。


    这不是最好的问题,而是什么时候使用什么。

    在'正常'情况下,一个简单的问题就足以发现我们是否需要继承或聚合。

  • 如果阶级或多或少原始类。 使用继承。 新类现在是原始类的一个子类。
  • 如果新班级必须原班级。 使用聚合。 新班级现在已经是原班级成员了。
  • 但是,有一个很大的灰色地带。 所以我们需要其他几个技巧。

  • 如果我们已经使用了继承(或者我们打算使用它),但是我们只使用部分接口,或者我们被迫覆盖很多功能来保持逻辑关联。 然后我们有一个很讨厌的气味,表明我们不得不使用聚合。
  • 如果我们使用聚合(或者我们计划使用它),但是我们发现我们需要复制几乎所有的功能。 然后我们有一种指向继承方向的气味。
  • 把它缩短。 如果部分界面未使用或必须更改以避免不合逻辑的情况,我们应该使用聚合。 我们只需要使用继承,如果我们几乎需要所有的功能而不做重大更改。 如有疑问,请使用Aggregation。

    对于我们有一个类需要原始类的部分功能的情况,另一种可能性是将原始类拆分为根类和子类。 让新类继承根类。 但你应该注意这一点,而不是造成不合逻辑的分离。

    让我们添加一个例子。 我们有一个班'狗'与方法:'吃','走','树皮','玩'。

    class Dog
      Eat;
      Walk;
      Bark;
      Play;
    end;
    

    我们现在需要一个'猫'类,需要'吃','走','普尔'和'玩'。 所以首先尝试从狗中扩展它。

    class Cat is Dog
      Purr; 
    end;
    

    看,好吧,但是等一下。 这只猫可以吠叫(猫爱好者会为此杀死我)。 而一只吠叫的猫则违背了宇宙的原则。 所以我们需要重写Bark方法,这样它就什么都不做。

    class Cat is Dog
      Purr; 
      Bark = null;
    end;
    

    好的,这有效,但味道不好。 所以让我们尝试一个聚合:

    class Cat
      has Dog;
      Eat = Dog.Eat;
      Walk = Dog.Walk;
      Play = Dog.Play;
      Purr;
    end;
    

    好的,这很好。 这只猫不再吠叫,甚至没有沉默。 但它仍然有一只想要外出的内部狗。 因此,让我们尝试解决方案三:

    class Pet
      Eat;
      Walk;
      Play;
    end;
    
    class Dog is Pet
      Bark;
    end;
    
    class Cat is Pet
      Purr;
    end;
    

    这更清洁。 没有内部的狗。 猫和狗是在同一水平。 我们甚至可以引入其他宠物来扩展模型。 除非是鱼,否则不会走路。 在这种情况下,我们需要重构。 但那是另一回事。


    在GOF开始时他们说

    赞成继承类继承的对象组合。

    这在这里进一步讨论


    差异通常表示为“是”和“具有”之间的差异。 继承,“是一种”关系,在Liskov替代原则中很好地概括了。 聚合,“有一个”关系就是这样 - 它表明聚合对象具有聚合对象之一。

    进一步的区别也存在 - C ++中的私有继承指示“按照”关系实现,也可以通过(非暴露)成员对象的聚合来建模。

    链接地址: http://www.djcxy.com/p/53941.html

    上一篇: Inheritance vs. Aggregation

    下一篇: How do Java Interfaces simulate multiple inheritance?