我来自PHP世界,很好奇为什么开发人员选择不添加构造函数(使用arg)继承的方式.从我的观点来看,它违反了DRY原则,重复了很多代码,具体取决于结构.我做了很少的研究 - PHP,Ruby,Python继承了构造函数.Java,C#,C++没有.C++ 0x具有明确定义继承的新功能.
那么程序员没有继承构造函数并且一次又一次地显式编写构造函数会有什么好处吗?
我对梅森的解释并不完全满意,所以我做了一些研究.
我发现语言设计者选择省略构造函数继承有两个关键原因,我可以更好地与之相关.
第一个原因是版本控制 - 如果在基类中引入了新的构造函数,或者如果在基类中更改了构造函数签名,则子类将继承这些更改,这可能导致不可预测的副作用.
这个问题经常出现在各种类型的框架中,通常,框架的API的一部分由您希望扩展的类组成,或者由您实现的抽象类 - 如果构造函数签名在框架类中发生更改,您将继承这些变化,大多数时候这些变化都没有意义.这被称为" 脆弱的基类 "问题.
第二个原因更多的是哲学方面,与第一个原因密切相关.构造函数在构造实例时定义了您的类作为该类的使用者所需的内容 - 因此它是关于类本身的需求.将此与方法进行对比,方法定义了类的实际行为 - 它是关于类的作用.基于此,哲学论证是,继承是关于行为,而不是关于需求.
这两个参数实际上是同一个硬币的两个方面:可以说,构造函数继承是脆弱的,因为构造函数定义了类的需求 - 例如,期望基类的作者能够预测所有需求是不合理的你的扩展课程.
或者,至少,做出这种假设(正如某些语言所做的那样)会极大地限制基类的有用性,因为你只能在扩展类中引入新行为,而不是任何新的需求.
我想到的另一件事是完整性问题 - 这是个人偏好的问题,但在继承构造函数的语言中,我经常发现你必须通过基类进行大量的挖掘来学习和理解所有的可以以不同的方式构造给定类的实例.
如果构造函数不能被继承,那就迫使任何类的作者明确表示构造一个类实例的所有方式 - 即使这些构造函数中的一些将是完全无关紧要的,事实上它们在类本身中定义,表明该类的作者确实认为它们有用,并且必须理解并考虑基类的所有需求,这可能是由不同的作者编写的.
而不是盲目地继承基类的需求,而不是主动展示是否考虑基类的需求是否符合扩展类的需要.
在实践中,我发现需求不一致.但即使它们确实一致,被迫思考它们如何对齐,也会导致更高的代码质量,我相信这是构造函数的非继承性由语言设计者选择的哲学 - 毕竟,语言也是如此好作为写入其中的代码.
OO 语言总是可以提供一个默认构造函数,它接受与超类构造函数相同的参数,并用它们调用超类的构造函数。没有什么理由不这样做,除非你必须考虑这样做并将其设计到语言中。
不提供默认构造函数意味着提供大量样板代码。举个例子:我想继承并[GestureDetector][1]
对其构建方法的行为进行微小的更改。GestureDetector 有O(30)
构造函数参数,我需要声明这些参数并简单地传递给 super 的调用。这些都对我试图解决的问题没有好处。我鼓励复制并粘贴 GestureDetector 的重新实现(这会减少工作量!),这都是因为 GestureDetector 没有提供简单的钩子来适应其行为。这本身就需要了解 Flutter 的较低层(这很重要)并维护在大量依赖项发生变化时中断的代码(这在 Flutter 中经常发生)。
我不同意 @mindplay.dk 的论点 - 如果超类构造函数发生更改,它会影响派生类,无论它是否定义了显式构造函数。大多数时候,简单地接受并传递具有相同名称的参数是正确的想法(很好,因为这可能不涉及新的工作)。如果没有,定义一个新的构造函数将承受与当前解决方案相同的维护成本。
被迫编写样板文件是语言开发不良的标志。Dart 当然有很多改进的方法,无论是在这方面还是在其他方面。添加 AST 宏可以解决这个问题和许多其他问题。现在是这些一流功能的时候了(朋友们,现在是 2020 年了!)。
这样我们就可以为构造函数和其他地方实现我们自己缺失的功能:
@inherit_super_constructor
class B extends GestureDetector{ ... }
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
我认为您可能不得不问 Dart 的设计者这个问题,但从继承树中省略构造函数的典型原因是类可能需要发生特定的构造时操作。在java中(例如),所有对象都继承自Object,它有一个零值构造函数。
所以如果你想创建一些像这样的对象:
public class Foo {
private final String bar;
public Foo(String barValue) {
this.bar = barValue;
}
}
Run Code Online (Sandbox Code Playgroud)
当有人调用父级的零参数构造函数时,您会对会发生什么感到不确定:
Foo bla = new Foo(); // runtime error or is bar null?
Run Code Online (Sandbox Code Playgroud)
当然,与所有语言功能一样,您可以想出可以指定此行为的方法,但显然 java 和 dart 的设计者不喜欢其中任何一个。
最后,我认为这一切都归结为哲学:php、ruby 等,朝着减少打字的方向发展,而 java、C#、dart 等,往往朝着更多打字和更少空间的方向发展造成混乱。