为什么更喜欢组合而不是继承?每种方法都有哪些权衡?什么时候应该选择继承而不是组合?


当前回答

继承是非常诱人的,尤其是来自程序领域的继承,它通常看起来很优雅。我的意思是,我需要做的就是将这一点功能添加到其他类中,对吗?嗯,问题之一是继承可能是最糟糕的耦合形式

基类通过以受保护成员的形式向子类公开实现细节来打破封装。这会使您的系统变得僵化和脆弱。然而,更悲惨的缺陷是新的子类带来了继承链的所有包袱和观点。

《继承是邪恶的:DataAnnotationsModelBinder的史诗般的失败》一文在C#中讲述了一个这样的例子。它显示了继承在何时应该使用组合以及如何重构组合。

其他回答

正如许多人所说的,我首先要检查是否存在“是”的关系。如果存在,我通常检查以下内容:

是否可以实例化基类。也就是说,基类是否可以是非抽象的。如果可以是非抽象的,我通常更喜欢写作

例如1。会计是员工。但我不会使用继承,因为Employee对象可以实例化。

例如2。书籍是SellingItem。SellingItem无法实例化-它是抽象概念。因此,我将使用遗传痤疮。SellingItem是一个抽象基类(或C#中的接口)

你觉得这种方法怎么样?

此外,我支持为什么要使用继承?

使用继承的主要原因不是作为组合的一种形式,而是为了让您可以获得多态行为。如果不需要多态性,那么可能不应该使用继承。

@马修。在https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448

继承的问题在于它可以用于两个正交的目的:接口(用于多态性)实现(用于代码重用)

参考

哪个班级设计更好?继承与聚合

假设飞机只有两个部分:发动机和机翼。然后有两种设计飞机等级的方法。

Class Aircraft extends Engine{
  var wings;
}

现在你的飞机可以从固定机翼开始并在飞行中将其改为旋转翼。它本质上是有翅膀的发动机。但如果我想改变呢发动机也在飞行?

基类Engine公开一个赋值函数以更改其财产,或我将飞机重新设计为:

Class Aircraft {
  var wings;
  var engine;
}

现在,我也可以随时更换发动机。

继承是一种非常强大的代码重用机制。但需要正确使用。如果子类也是父类的子类型,我会说继承是正确使用的。如上所述,利斯科夫替代原则是这里的关键点。

子类与子类型不同。您可以创建不是子类型的子类(此时应该使用组合)。为了理解什么是子类型,让我们开始解释什么是类型。

当我们说数字5是整数类型时,我们说明5属于一组可能的值(例如,请参阅Java原语类型的可能值)。我们还声明,我可以对值执行一组有效的方法,如加法和减法。最后,我们要说明的是,有一组财产总是可以满足的,例如,如果我将值3和5相加,结果会得到8。

举另一个例子,考虑抽象数据类型,整数集合和整数列表,它们可以保存的值仅限于整数。它们都支持一组方法,如add(newValue)和size()。而且它们都有不同的财产(类不变量),Sets不允许重复,而List允许重复(当然还有它们都满足的其他财产)。

子类型也是一种类型,它与另一种类型(称为父类型(或父类型))有关系。子类型必须满足父类型的功能(值、方法和财产)。这种关系意味着在任何期望超类型的上下文中,它都可以被子类型替代,而不会影响执行的行为。让我们去看一些代码来举例说明我在说什么。假设我写了一个整数列表(用某种伪语言):

class List {
  data = new Array();

  Integer size() {
    return data.length;
  }

  add(Integer anInteger) {
    data[data.length] = anInteger;
  }
}

然后,我将整数集合写成整数列表的子类:

class Set, inheriting from: List {
  add(Integer anInteger) {
     if (data.notContains(anInteger)) {
       super.add(anInteger);
     }
  }
}

我们的整数集合类是整数列表的子类,但不是子类型,因为它不满足列表类的所有特性。满足了方法的值和签名,但不满足财产。add(Integer)方法的行为已经明显改变,没有保留父类型的财产。从类的客户端的角度思考。他们可能会收到一组整数,其中需要一个整数列表。客户端可能希望添加一个值,并将该值添加到列表中,即使该值已经存在于列表中。但如果价值存在,她就不会有这种行为。她大吃一惊!

这是一个不正确使用继承的典型例子。在这种情况下使用合成。

(片段来自:正确使用继承)。

我同意@帕维尔的说法,他说,有地方可以组成,也有地方可以继承。

我认为如果你的答案是肯定的,那么应该使用继承。

您的类是从多态性中受益的结构的一部分吗?例如,如果您有一个Shape类,它声明了一个名为draw()的方法,那么我们显然需要Circle和Square类作为Shape的子类,这样它们的客户端类将依赖于Shape而不是特定的子类。您的类是否需要重用其他类中定义的任何高级交互?如果没有继承,模板方法设计模式将不可能实现。我相信所有可扩展框架都使用这种模式。

然而,如果您的意图纯粹是代码重用,那么组合很可能是更好的设计选择。

如果你明白其中的区别,那么解释起来就更容易了。

程序代码

这方面的一个例子是不使用类的PHP(尤其是在PHP5之前)。所有逻辑都编码在一组函数中。您可以包含包含助手函数等的其他文件,并通过在函数中传递数据来执行业务逻辑。随着应用程序的增长,这可能很难管理。PHP5试图通过提供更面向对象的设计来解决这一问题。

遗产

这鼓励使用类。继承是OO设计的三大原则之一(继承、多态性、封装)。

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

这是工作中的继承。员工“是”个人或继承自个人。所有继承关系都是“is-a”关系。Employee还隐藏Person的Title属性,意思是Employee.Title将返回Employees的Title,而不是Person。

作文

组合比继承更受欢迎。简单地说,你应该:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

组成通常是“有”或“使用”关系。这里Employee类有一个Person。它不从Person继承,而是将Person对象传递给它,这就是它“有”Person的原因。

继承上的组合

现在,假设您要创建一个管理器类型,这样您就可以:

class Manager : Person, Employee {
   ...
}

但是,如果Person和Employee都声明了Title,那么这个示例会很好用?经理头衔应该返回“运营经理”还是“先生”?在合成中,这种模糊性得到了更好的处理:

Class Manager {
   public string Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Manager对象由Employee和Person组成。标题行为取自雇员。这种显式组合消除了其他问题中的歧义,您将遇到更少的错误。