In the Dart language tour for constructors it gives an example of a generative constructor:
class Point {
double x, y;
Point(double x, double y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
Later it gives an example of a named constructor:
class Point {
double x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
That led me to believe that when a constructor uses the same name as a class it is a generative constructor:
Point(this.x, this.y);
But when there is an additional identifier then it is a named constructor:
Point.origin() {
x = 0;
y = 0;
}
However, on another one of my answers a Dart expert changed my wording of "named constructor" to "generative constructor". That made me realize that I may have misunderstood the difference between them. Is a named constructor a subset of a generative constructor? If so, how do I call a constructor that only has the name of the class without an additional identifier attached?
The term "named constructor" doesn't appear to be in the language spec.
In Dart any constructor is either a generative constructor or a factory constructor.
If it says factory
in front, it's a factory constructor, otherwise it's a generative constructor.
A generative constructor always creates a new instance of the precise class it belongs to. A factory constructor is (almost) just a static function with a return type which is the type of the class it belongs to. It can return any subtype of that, but it doesn't create any new objects by itself.
Constructors can be named or unnamed. For the class Foo
, the constructor named Foo
is the "unnamed" (or, really, "empty-named") constructor, and Foo.someName
is a named constructor. Whether a constructor is named or unnamed is independent of whether it's generative or a factory.
Dart doesn't have overloading (multiple declarations with the same name in the same scope, typically distinguished by the argument types), so without named constructors, it would only be possible to have one constructor per class. Named constructors allows a class to have as many constructors as it wants to, and each one can be any of the variations of constructors that the language allows.
Constructors can be either forwarding or, for lack of a better name, non-forwarding. A forwarding generative constructor must forward to a generative constructor of the same class. Example:
class Point {
final double x, y;
const Point(this.x, this.y); // Generative, unnamed, non-forwarding, const.
const Point.diagonal(double xy) : this(xy, xy); // Generative, named, forwarding, const.
}
A forwarding factory constructor forwards its parameters to a different constructor. Example:
abstract class Point {
double get x;
double get y;
const factory Point(this.x, this.y) = _Point; // Factory, unnamed, forwarding, const.
}
class _Point implements Point {
final double x, y;
const _Point(this.x, this.y); // Generative, unnamed, non-forwarding, const.
}
The two kinds of forwarding constructors, generative and factory, are not really related. They work in quite different ways, but both delegate the job of returning the object to another constructor, and they cannot have a body. Again, being named is independent of all of this.
Finally, constructors can be const
or not.
A const generative constructor is either forwarding to another const generative constructor, and the : this(...)
arguments must be potentially constant expressions, or it's non-forwarding, and then all initializer expressions must be potentially constant expressions and there cannot be a body.
A const factory constructor must be forwarding to another const constructor (whether factory or generative). Const constructors cannot have a body, and non-forwarding factory constructors must have a body.
Of all these different constructors, only the non-forwarding generative constructor can actually create a new object itself. That's where the real action happens. That's the most fundamental constructor, and it can look something like:
Foo.bar(this.value, int otherValue) : _otherValue = otherValue, super.bar(42) {
this.doSomething();
}
A non-redirecting factory constructor is the only place you can use initializing formals (the parameter of the form this.value
). The initializer list can initialize instance variables declared in the same class, but can obviously be empty if there are none. The super-invocation at the end must call a generative constructor of the superclass, but defaults to super()
if omitted. The body is the first place the new object becomes available (as this
), but the body can be omitted (replaced by ;
) if it's empty. That's why the simplest constructor is just Foo();
(which is also the default constructor that you get if you declare no other constructor).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With