Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Dart does not inherit constructor?

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?

like image 706
user2216697 Avatar asked Mar 27 '13 17:03

user2216697


2 Answers

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.

like image 96
mindplay.dk Avatar answered Sep 30 '22 18:09

mindplay.dk


OO languages can always provide a default constructor that accepts the same parameters as the superclass constructor and calls the superclass's constructor with them. There's not really a good reason not to do this, except you have to think to do it and design it into the language.

Not providing a default constructor means providing a ton of boilerplate code. Case in point: I'd like to inherit [GestureDetector][1] to make a minor changes to the behavior of its build method. GestureDetector has O(30) constructor parameters, which I'd need to declare and simply pass through to the call to super. None of this benefits the problem I'm trying to solve. I'm encouraged copy and paste a reimplementation of GestureDetector (it would be less work!), all because GestureDetector didn't provide trivial hooks for adapting its behavior. This itself entails understanding lower layers of flutter (which is significant) and maintaining code that breaks when its much larger number of dependencies change (which is happening frequently in Flutter).

I don't agree with @mindplay.dk's arguments - if the superclass constructor changes, it affects the derived class whether on not it has defined an explicit constructor. Most of the time, simply accepting and passing through parameters with the same name is the right think to do (great since that may involve no new work). If not, define a new constructor suffers same the maintenance costs as the current solution.

Being forced to write boilerplate is the sign of poorly developed language. And Dart certainly has many ways to improve, here and in others. The addition of AST macros would allow workarounds to this and many other issues. Its high time these were first class features (its 2020, people!).

With that we could implement our own missing features, for constructors and elsewhere:

@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;
}
like image 25
user48956 Avatar answered Sep 30 '22 19:09

user48956