8

我来自 PHP 世界,很好奇为什么开发人员选择不将构造函数(带 arg)添加到继承中的方式。在我看来,根据结构重复大量代码违反了 DRY 原则。我做了很少的研究——PHP、Ruby、Python 继承了构造函数。Java、C#、C++ 不是。C++0x 具有显式定义继承的新特性。

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

4

4 回答 4

16

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

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

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

这个问题经常出现在各种类型的框架中,通常,框架 API 的一部分由您希望扩展的类或您要实现的抽象类组成 - 如果框架类中的构造函数签名发生变化,您会继承这些更改,大多数情况下这些更改是没有意义的。这被称为“脆弱基类”问题。

第二个原因更多是哲学方面的,并且与第一个密切相关。当你构造一个实例时,构造函数定义你的类对你的要求,作为那个类的消费者——所以它是关于类本身的需求。将此与定义类的实际行为的方法进行对比——它与类的作用有关。基于此,哲学论点是继承是关于行为,而不是关于需求

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

或者,至少,做出这种假设(就像某些语言所做的那样)会大大限制基类的有用性,因为您只能在扩展类中引入新行为,而永远不会引入任何新需求

我想到的另一件事是完整性问题 - 这是个人喜好问题,但是在继承构造函数的语言中,我经常发现你必须做很多挖掘基类才能学习和理解所有构造给定类的实例的不同方式。

如果构造函数不能被继承,这将迫使任何类的作者明确说明期望构造类的实例的所有方式——即使其中一些构造函数将是完全微不足道的,事实上它们是在类本身中定义的,表明该类的作者确实认为它们很有用,并且必须理解和考虑可能由不同作者编写的基类的所有需求。

与一味地继承基类的需求相反,没有主动论证任何关于基类的需求是否与扩展类的需求一致的考虑。

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

于 2013-07-04T14:34:19.303 回答
4

OO 语言总是可以提供一个默认构造函数,它接受与超类构造函数相同的参数,并使用它们调用超类的构造函数。没有真正的理由不这样做,除非您必须考虑这样做并将其设计到语言中。

不提供默认构造函数意味着提供大量样板代码。恰当的例子:我想继承[GestureDetector][1]对其构建方法的行为进行微小的更改。GestureDetector 具有O(30)构造函数参数,我需要声明并简单地传递给对 super 的调用。这些都不利于我试图解决的问题。我被鼓励复制并粘贴 GestureDetector 的重新实现(它的工作量会减少!),这一切都是因为 GestureDetector 没有提供用于调整其行为的简单挂钩。这本身需要理解 Flutter 的较低层(这很重要)并维护在其大量依赖项发生变化时会中断的代码(这在 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;
}
于 2020-04-08T20:19:23.100 回答
2

我想你可能不得不问 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 等倾向于朝着更多打字和更少空间的方向发展为混乱。

于 2013-03-27T22:38:24.250 回答
0

你可以试试下面的..

main(){
  ParentData parentData = SettingsData.fromJson();
}

abstract class ParentData{
  ParentData.formJson();
}

class SettingsData extends ParentData{
  SettingsData.fromJson():super.formJson(){
    print("Child json");
  }
}

输出:子 json

于 2021-11-08T04:25:14.733 回答