为什么Dart不继承构造函数?

时间:2013-03-27 17:23:41

标签: inheritance dart

我来自PHP世界,我很好奇为什么开发人员选择不添加构造函数(使用arg)继承的方式。从我的观点来看,它违反了DRY原则,重复了很多代码,具体取决于结构。我做了很少的研究 - PHP,Ruby,Python继承了构造函数。 Java,C#,C ++没有。 C ++ 0x具有明确定义继承的新功能。

那么程序员没有继承构造函数并且一次又一次地显式编写构造函数会有什么好处吗?

4 个答案:

答案 0 :(得分:6)

我对梅森的解释并不完全满意,所以我做了一些研究。

我发现语言设计者选择省略构造函数继承有两个关键原因,我可以更好地与之相关。

第一个原因是版本控制 - 如果在基类中引入了新的构造函数,或者如果在基类中更改了构造函数签名,则子类将继承这些更改,这可能导致不可预测的副作用。

这个问题经常出现在各种类型的框架中,通常,框架的API的一部分由您希望扩展的类组成,或者抽象类供您实现 - 如果构造函数签名在框架类中发生更改,你会继承这些变化,大多数时候这些变化都没有意义。这被称为“Fragile Base Class”问题。

第二个原因更多的是哲学方面,与第一个原因密切相关。在构造实例时,构造函数定义您的类需要您作为该类的使用者 - 因此它是关于类本身的需求。将此与方法进行对比,方法定义了类的实际行为 - 它是关于类所做的。基于此,哲学论点是,继承是关于行为,而不是关于需要

这两个论点实际上是同一个硬币的两个方面:可以说,构造函数继承是脆弱的因为构造函数定义了类的需求 - 例如,它是不合理的期望基类的作者能够预测扩展类的所有需求。

或者,至少,做出这样的假设(正如某些语言所做的那样)会大大限制基类的有用性,因为你只能在扩展类中引入新的行为,从来没有任何新的需要

我遇到的另一件事是完整性问题 - 这是个人偏好的问题,但在继承构造函数的语言中,我经常发现你必须通过基类进行大量挖掘来学习和了解给定类的实例可能构造的所有不同方式。

如果构造函数不能被继承,那么迫使任何类的作者明确表示构造一个类实例的所有方式 - 即使这些构造函数中的一些将完全无关紧要,它们是在类本身中定义的事实,表明该类的作者确实认为它们有用,并且必须理解并考虑基类的所有需求,这可能是由不同的作者编写的。 / p>

与盲目继承基类的需求相反,而不是主动证明基类的需求是否符合扩展类的需要。

在实践中,我发现需求并不一致。但即使它们确实一致,被迫思考它们如何对齐,也会导致更高的代码质量,我相信这是构造函数的非继承性由语言设计者选择的哲学 - 毕竟,语言也是如此好作为写入其中的代码。

答案 1 :(得分:3)

我认为你可能不得不问Dart的设计者这个问题,但是从继承树中省略构造函数的典型原因是类可能具有需要发生的特定构造时操作。在java(例如)中,所有对象都从Object继承,它具有零值构造函数。

所以如果你想创建一个像这样的对象:

public class Foo {
   private final String bar;

   public Foo(String barValue) {
       this.bar = barValue;
   }
}

当有人打电话给父母的零参数构造函数时,你会遇到一些不确定性:

Foo bla = new Foo(); // runtime error or is bar null?

当然,与所有语言功能一样,您可以想出可以指定此行为的方法,但很明显,java和dart的设计师并不喜欢它们中的任何一种。

最后,我认为这一切都归结为哲学:php,ruby等,朝着更少打字的方向发展,因为java,C#,dart等往往更倾向于打字的方向并且减少了混淆的空间。

答案 2 :(得分:1)

OO语言始终可以提供默认的构造函数,该构造函数接受与超类构造函数相同的参数,并使用它们调用超类的构造函数。确实没有充分的理由不这样做,除非您必须考虑这样做并将其设计为语言。

不提供默认构造函数意味着提供大量样板代码。恰当的例子:我想继承[GestureDetector][1]对其构建方法的行为进行微小的更改。 GestureDetector具有O(30)构造函数参数,我需要对其进行声明,并直接传递给对super的调用。这些都无济于事我要解决的问题。我被鼓励复制并粘贴GestureDetector的重新实现(这会减少工作量!),这是因为GestureDetector并没有提供用于调整其行为的简单钩子。这本身就需要了解颤动的较低层(这很重要),并维护在其依赖项数量大量变化时会中断的代码(这在Flutter中经常发生)。

我不同意@ mindplay.dk的参数-如果超类构造函数发生变化,则无论是否定义了显式构造函数,都会影响派生类。在大多数情况下,简单地接受并传递具有相同名称的参数是正确的想法(这很重要,因为这可能不涉及任何新工作)。如果没有,那么定义一个新的构造函数将遭受与当前解决方案相同的维护成本。

被强迫写样板是语言发展不佳的标志。 Dart在这里和其他地方当然有很多改进的方法。 AST宏的添加将允许解决此问题和许多其他问题。高潮时期这些都是一流的功能(2020年,人们!)。

这样,我们可以为构造函数和其他地方实现我们自己缺少的功能:

@inherit_super_constructor 
class B extends GestureDetector{ ... }
class B extends A{
  @variadic_parameters   // Infers requirements from what is called
  B(*parent_positional, **parent_named, {this.extra}) : super(*parent_positional, **parent_named);

  String extra;
}

答案 3 :(得分:0)

可以! 在Flutter中,如果我们有

class Bicycle {
  int wheelsNumber;

  Bicycle(this.wheelsNumber); // constructor
}

和从中继承的孩子

class ElectricBike extends Bicycle {
  int chargingTime;

  // you can call on the super constructor as such
  ElectricBike(int wheelsNumber, this.chargingTime) : super(wheelsNumber);
}
相关问题