Why Dart does not inherit constructor?

2019-07-31 10:28发布

问题:

I am coming from PHP world and am curious why developers choose the way not adding constructor (with arg) to inheritance. From my view it violates DRY principle by repeating a lot of code, depending on structure. I did little research - PHP, Ruby, Python inherits constructor. Java, C#, C++ not. C++0x have new feature explicitly defining inheritances.

So is there any advantage for programmer not having constructor inherited and explicitly write constructor again and again?

回答1:

I think you'd probably have to ask Dart's designers that question, but the typical reason for omitting constructors from the inheritance tree is that a class may have specific construct-time operations that need to happen. In java (for example), all objects inherit from Object, which has a zero value constructor.

So if you wanted to create some object like this:

public class Foo {
   private final String bar;

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

You'd be left with some uncertainty as to what happens when someone called the parent's zero parameter constructor:

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

Of course, as with all language features, you can think of ways that this behavior could be specified, but clearly java and dart's designers didn't like any of them.

In the end, I think it all comes down to philosophy: php, ruby, etc., goes in the direction of less typing where as java, C#, dart, etc., tend to go in the direction of more typing and less room for confusion.



回答2:

I was not fully satisfied with Mason's explanation, so I did a little research.

I found there are two key reasons why language designers choose to omit constructor inheritance, that I can better relate to.

The first reason is versioning - if a new constructor is introduced in a base-class, or if a constructor signature is changed in a base-class, sub-classes will inherit those changes, which can lead to unpredictable side effects.

This problem often surfaces in frameworks of various types, where often, a part of the framework's API consists of classes that you're expected to extend, or abstract classes for you to implement - if the constructor signatures change in a framework class, you would inherit those changes, which most of the time isn't going to make sense. This is known as the "Fragile Base Class" problem.

The second reason is more on the philosophical side, and relates closely to the first. Constructors define what your class requires from you, as a consumer of that class, when you construct an instance - so it's about the needs of the class itself. Contrast this with methods, which define the actual behaviors of the class - it's about what the class does. Based on that, the philosophical argument, is that inheritance is about behavior, not about needs.

These two arguments are actually two sides of the same coin: arguably, constructor inheritance is fragile because constructors define the needs of a class - for example, it's not reasonable to expect the author of a base-class to be able to predict all the needs of your extended class.

Or, at least, making that assumption (as some languages do) would limit the usefulness of a base-class considerably, since you would only be able to introduce new behavior in your extended class, never any new needs.

Another thing that occurred to me, is the matter of completeness - this is a matter of personal preference, but in languages that inherit constructors, I often find that you have to do a lot of digging through base-classes to learn and understand all the different ways an instance of a given class might get constructed.

If constructors can't be inherited, that forces the author of any class to be explicit about all the ways in which an instance of a class is expected to be constructed - even if some of these constructors are going to be totally trivial, the fact that they are defined in the class itself, demonstrates that the author of that class did consider them useful, and had to understand and consider all the needs of the base-class, which may have been written by a different author.

As opposed to blindly inheriting the needs of the base-class, without actively demonstrating any consideration as to whether the needs of the base-class align with the needs of the extended class.

In practice, I find that the needs do not align. But even when they do align, being forced to think about how they align, leads to higher code quality, and I believe that's the philosophy on which non-inheritance of constructors is chosen by language designers - after all, a language is only as good as the code that gets written in it.



回答3:

It does! In Flutter, if we have a

class Bicycle {
  int wheelsNumber;

  Bicycle(this.wheelsNumber); // constructor
}

and a child inheriting from it

class ElectricBike extends Bicycle {
  int chargingTime;

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