To test my understanding of Dart, I made a 2D immutable vector that stores not only its x and y components, but also its angle and length. They are computed only at construction out of the x and y values. I soon realized that final fields need to be set with initializer lists or the this
-parameter shortcut, which don't allow for much computed values. Like this answer points out, I had to use a factory constructor to create my vector out of its x and y components. The factory constructor then validates input and computes the length and angle before calling the private constructor.
import 'dart:math';
class ImmutableVector {
// private fields
final double _x;
final double _y;
final double _angle;
final double _length;
// getters
double get x => _x;
double get y => _y;
double get angle => _angle;
double get length => _length;
/// Constructs a vector out of cartesian components.
///
/// Converts both arguments to doubles.
/// Null values will be treated as zeroes.
factory ImmutableVector.xy(num x, num y) {
x ??= 0.0;
y ??= 0.0;
x = x.toDouble();
y = y.toDouble();
var angle = atan2(y, x) % (2*PI);
var length = sqrt(pow(x, 2) + pow(y, 2));
return new ImmutableVector._raw(x, y, angle, length);
}
/// Constructs a vector by setting the fields directly.
const ImmutableVector._raw(this._x, this._y, this._angle, this._length);
}
But it came to my attention that I could not make the factory constructor const, because const factories can only be redirecting constructors. Is there absolutely no way to make my vector have code in its constructor and still have it an immutable with const constructors? And if so, why?
I'll also mention that I used to throw errors if the values passed were null, but I made it default to zero so I could actually use initializer lists. I then tried to do this, and turns out it works when the constructor isn't a factory:
ImmutableVector.xy(num x, num y) : this._raw(
x = (x ?? 0.0).toDouble(),
y = (y ?? 0.0).toDouble(),
atan2(y, x) % (2*PI),
sqrt(pow(x, 2) + pow(y, 2)));
But as soon as I try to make it const, it tells me that the code in the initializer list contains non-compile-time constants.
The only immutable vector I could find in dart was here on GitHub, and it doesn't do any kind of null-validation or computation on constructor parameters, relying entirely on the fact that methods will break at some point on null-vectors. It also has a constructor that creates a unit vector out of another one with poor performance and repetition thanks to the obligatory initializer list:
const Vector.unit(final num x, final num y, final num z) :
this(x / PMath.len(x, y, z), y / PMath.len(x, y, z), z / PMath.len(x, y, z));
So what should be my conclusion? Did I miss a feature that makes this possible, or should I just give up on using const for this class?
In order to use a const constructor to actually create a compile-time constant object, you then replace "new" with "const" in a "new"-expression. You can still use "new" with a const-constructor, and it will still create an object, but it will just be a normal new object, not a compile-time constant value.
A factory constructor is a constructor that can be used when you don't necessarily want a constructor to create a new instance of your class. This might be useful if you hold instances of your class in memory and don't want to create a new one each time (or if the operation of creating an instance is costly).
Overview. In Dart, we use the factory keyword to identify a default or named constructor. We use the factory keyword to implement constructors that do not produce new instances of an existing class.
Factory constructors return am instance of the class, but it doesn't necessarily create a new instance. Factory constructors might return an instance that already exists, or a sub-class. Rules for factory constructors: Factory constructors do use the return key word.
A factory constructor is a constructor that can be used when you don't necessarily want a constructor to create a new instance of your class. This might be useful if you hold instances of your class in memory and don't want to create a new one each time (or if the operation of creating an instance is costly).
It is probably still good practice to use a factory constructor to create instances of classes instead of static methods to make the purpose of object creation more obvious.
As an expension to the previous features, using the short-hand for redirecting to a different constructor it is possible to have “const factories”: Combined with the fact that factory constructors can be unnamed, this covers the entire spectrum of the features a constructor can have.
This can increase the readability quite a bit as you don’t need to give a meaningless name to the constructor. The difference in verbosity between a static method and a named factory constructor when instantiating a simple class is negligible. But things can get very different when you are trying to instantiate a complex class, such as:
Dart doesn't execute Dart code during compilation. This is the reason const
constructors can't have a body and why there is no other way to work around this limitation.
If you want to execute code when an instance is created, just don't make use of const
. Const isn't that important in Dart anyway. There were even discussions to remove it from the language because the benefits are not big enough.
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