比继承更偏爱组成?

时间:2020-03-05 18:49:07  来源:igfitidea点击:

为什么更偏重于继承而不是继承?每种方法都有哪些取舍?什么时候应该选择继承而不是组成?

解决方案

回答

将遏制视为有关系。汽车"具有"发动机,人"具有"名称,等等。

将继承视为一种关系。汽车"是"车辆,人"是"哺乳动物,等等。

我不相信这种方法。我直接从Steve McConnell撰写的第二版Code Complete的6.3节中获得了它。

回答

根据我的经验,继承关系为"是"时应使用继承,继承关系为"必须"时应使用组合。即使这样,我仍然认为我们应该始终倾向于合成,因为它消除了很多复杂性。

回答

在Java或者C#中,对象一旦实例化就无法更改其类型。

因此,如果对象需要显示为不同的对象或者根据对象的状态而不同地表现,请使用"组成":请参阅状态和策略设计模式。

如果对象必须是同一类型,则使用继承或者实现接口。

回答

继承功能非常强大,但是我们不能强制继承(参见:圆椭圆问题)。如果我们不能完全确定真正的"是"子类型关系,那么最好使用组合。

回答

另一个非常务实的原因是,首选组合而不是继承,这与域模型有关,并将其映射到关系数据库。很难将继承映射到SQL模型(我们最终会遇到各种各样的变通办法,例如创建不经常使用的列,使用视图等)。一些ORML试图解决这个问题,但是它总是很快变得复杂。通过两个表之间的外键关系可以轻松地对组合进行建模,但是继承要困难得多。

回答

除了有一个考虑因素外,还必须考虑对象必须经历的继承的"深度"。超过五,六个继承级别的任何事物都可能导致意外的强制转换和装箱/拆箱问题,在这种情况下,明智的做法是组成对象。

回答

优先考虑组成而不是继承,因为它在以后更易于延展/更容易修改,但不要使用总是组合的方法。通过组合,可以轻松地通过"依赖注入/设置器"即时更改行为。继承更为严格,因为大多数语言不允许我们从一种以上的类型派生。因此,一旦从TypeA派生,鹅或者多或者少就会被煮熟。

我对以上的酸测试是:

  • TypeB是否要公开TypeA的完整接口(所有公共方法不少于此),以便可以在需要TypeA的地方使用TypeB?表示继承。

例如塞斯纳双翼飞机将展示飞机的完整界面,甚至更多。因此,它很适合从飞机中派生。

  • TypeB是否只希望TypeA公开某些/部分行为?表示需要组成。

例如鸟类可能只需要飞机的飞行行为。在这种情况下,将其提取为接口/类/两者并使其成为这两个类的成员是有意义的。

更新:刚刚回到我的答案,现在似乎没有完整提及Barbara Liskov的Liskov替代原理,这是不完整的,以检验"我是否应该继承这种类型?"

回答

当我们要"复制" /公开基类的API时,可以使用继承。当我们只想"复制"功能时,请使用委托。

这样的一个示例:我们想从列表中创建堆栈。堆栈仅具有弹出,推入和窥视。如果我们不希望在栈中使用push_back,push_front,removeAt等功能,则不应使用继承。

回答

简而言之,我会同意"优先考虑组成而不是继承",但对我而言,这听起来常常像是"优先考虑土豆而不是可口可乐"。有继承的地方和组成的地方。我们需要了解差异,然后这个问题就会消失。对我而言,真正的含义是"如果我们要使用继承,请三思而后行,我们很可能需要合成"。

当我们想吃东西时,我们应该选择土豆而不是可口可乐,而当我们想要喝时,我们应该更喜欢可口可乐。

创建子类不仅仅意味着调用超类方法的简便方法。当子类" is-a"超类在结构上和功能上都可以使用时,我们应该使用继承。如果不是这种情况,那不是继承,而是其他东西。合成是指对象由另一个对象组成或者与它们有某种关系。

因此对我来说,好像某人不知道是否需要继承或者构成,真正的问题是他不知道自己是否想喝酒或者吃东西。多考虑问题领域,更好地理解它。

回答

如果我们了解它们之间的区别,则更容易解释。

程序代码

一个这样的例子是不使用类的PHP(特别是在PHP5之前)。所有逻辑都编码在一组函数中。我们可能会包含其他包含助手功能的文件,等等,并通过在功能中传递数据来进行业务逻辑。随着应用程序的增长,这可能很难管理。 PHP5试图通过提供更多面向对象的设计来对此进行补救。

遗产

这鼓励使用类。继承是OO设计的三个宗旨之一(继承,多态,封装)。

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

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

这是工作中的继承。雇员"是"个人或者从个人继承。所有继承关系都是"是"关系。 Employee还会遮盖Person的Title属性,这意味着Employee.Title将返回Employee而不是Person的Title。

作品

组合优于继承。简单地说,我们将拥有:

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的原因。

继承而不是继承

现在说我们要创建一个Manager类型,因此我们将获得以下结果:

class Manager : Person, Employee {
   ...
}

此示例可以正常工作,但是,如果Person和Employee都声明为" Title",该怎么办? Manager.Title应该返回"运营经理"还是"先生"?在组成下,这种歧义可以更好地解决:

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

Manager对象由Employee和Person组成。标题行为来自员工。这种明确的构成消除了其他方面的歧义,并且我们将遇到较少的错误。

回答

继承在子类和超类之间建立了牢固的关系。子类必须了解超类的实现细节。当我们必须考虑如何扩展超级类时,创建超级类要困难得多。我们必须仔细记录类不变式,并声明内部可重写方法使用哪些其他方法。

如果层次结构确实表示"是一个关系",则继承有时会很有用。它与开放式封闭原则有关,该原则指出,应关闭类以进行修改,但可以扩展。这样,我们就可以拥有多态性。具有处理超级类型及其方法的通用方法,但是通过动态调度,将调用子类的方法。这是灵活的,并有助于创建间接功能,这在软件中必不可少(以较少的了解实现细节)。

但是,继承很容易被过度使用,并且由于类之间的依赖关系而增加了额外的复杂性。由于层级和方法调用的动态选择,也很难理解在程序执行期间发生的情况。

我建议使用合成作为默认设置。它更具模块化,并具有后期绑定的优势(我们可以动态更改组件)。另外,单独测试这些东西也更容易。而且,如果我们需要使用类中的方法,则不必强制我们采用某种形式(Liskov替换原理)。

回答

继承带来了所有不可否认的好处,这是它的一些缺点。

继承的缺点:

  • 我们不能在运行时更改从超类继承的实现(显然是因为继承是在编译时定义的)。
  • 继承将子类暴露给其父类的实现细节,这就是为什么人们常说继承破坏了封装(从某种意义上说,我们实际上只需要专注于接口而不是实现,因此不总是首选通过子类进行重用)。
  • 继承提供的紧密耦合使子类的实现与超类的实现非常紧密地绑定在一起,从而父级实现中的任何更改都将迫使子类进行更改。
  • 通过子类进行过多的重用也会使继承堆栈变得非常深且非常混乱。

另一方面,对象组成是在运行时通过对象获取对其他对象的引用来定义的。在这种情况下,这些对象将永远无法访问彼此的受保护数据(没有封装中断),并且将被迫尊重彼此的接口。并且在这种情况下,实现依赖也将比继承的情况少很多。

回答

我们想强迫自己(或者另一个程序员)坚持什么,以及何时让自己(或者另一个程序员)拥有更大的自由。有人争辩说,当我们想强迫某人处理/解决特定问题,以使他们不会朝错误的方向前进时,继承很有用。

Is-a和Has-a是有用的经验法则。

回答

继承是非常诱人的,尤其是从程序领域来的继承,它看起来通常看起来很优雅。我的意思是,我需要做的就是将这一功能添加到其他类中,对吗?好吧,问题之一是

继承可能是最糟糕的耦合形式

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

文章"继承是邪恶的:DataAnnotationsModelBinder的史诗失败"将逐步介绍C#中的一个示例。它显示了应该使用合成时继承的使用以及如何重构。

回答

我们需要看一下Bob叔叔的SOLID类设计原理中的Liskov替代原理。 :)

回答

假设飞机只有两个部分:引擎和机翼。
然后,有两种方法可以设计飞机等级。

Class Aircraft extends Engine{
  var wings;
}

现在,飞机可以从固定机翼开始
并将它们动态更改为旋转式机翼。本质上是
带翅膀的引擎。但是如果我想改变怎么办
引擎也在飞行中?

基类" Engine"中的任何一个都会公开一个更改器以更改其
属性,或者我将"飞机"重新设计为:

Class Aircraft {
  var wings;
  var engine;
}

现在,我也可以随时更换引擎。