Constructors in Dart
class Robot { | |
Robot(); | |
} | |
// or if no arguments | |
class Robot { | |
} | |
// normal instanciation | |
var robot = Robot(); | |
class Robot { | |
double height; | |
Robot(height) : this.height = height; | |
} | |
class Robot { | |
double height; | |
Robot(data) : this.height = data.physics.raw['heightInFt']; | |
} | |
//short cut | |
class Robot { | |
double height; | |
Robot(this.height); | |
} | |
// instanciation with param | |
var r = Robot(5); | |
// instanciation with param that is an Object like | |
var r = Robot(myData); | |
class Robot { | |
static mToFt(m) => m * 3.281; | |
double height; // in ft | |
Robot(height) : this.height = mToFt(height); | |
} | |
// extending | |
class Machine { | |
String name; | |
Machine(this.name); | |
} | |
class Robot extends Machine { | |
static mToFt(m) => m * 3.281; | |
double height; | |
Robot(height, name) : this.height = mToFt(height), super(name); | |
} | |
// adding guards | |
class Robot { | |
final double height; | |
Robot(height) : this.height = height, assert(height > 4.2); | |
} | |
// simple example | |
class Robot { | |
double height; | |
Robot(this.height); | |
} | |
main() { | |
var r = Robot(5); | |
print(r.height); // 5 | |
} | |
// you can reassign value to the height | |
main() { | |
var r = Robot(5); | |
r.height = 6; | |
print(r.height); // 6 | |
} | |
// that way you make the param private and expose a public getter | |
class Robot { | |
double _height; | |
Robot(this._height); | |
get height { | |
return this._height; | |
} | |
} | |
// or simpler | |
class Robot { | |
double _height; | |
Robot(this._height); | |
get height => _height; | |
} | |
// full equivalend of the first robot | |
class Robot { | |
double _height; | |
Robot(this._height); | |
get height => _height; | |
set height(value) => _height = value; | |
} | |
// not possible to use setters as param to the constructor | |
class Robot { | |
double _height; | |
Robot(this.height); // ERROR: 'height' isn't a field in the enclosing class | |
get height => _height; | |
set height(value) => _height = value; | |
} | |
// use the setter in the constructor body | |
class Robot { | |
double _height; | |
Robot(h) { | |
height = h; | |
} | |
get height => _height; | |
set height(value) => _height = value; | |
} | |
// named arguments | |
class Robot { | |
final double height; | |
final double weight; | |
final List<String> names; | |
Robot({ this.height, this.weight, this.names }); | |
} | |
main() { | |
final r = Robot(height: 5, names: ["Walter"]); | |
print(r.height); // 5 | |
} | |
// adding guards to named arguments constructors | |
class Robot { | |
final double height; | |
final double weight; | |
final List<String> names; | |
Robot({ this.height, @required this.weight, this.names }); | |
} | |
// or | |
class Robot { | |
final double height; | |
final double weight; | |
final List<String> names; | |
Robot({ this.height, this.weight, this.names }) : assert(weight != null); | |
} | |
// cannot make positional params private | |
class Robot { | |
final double _height; | |
final double _weight; | |
final List<String> _names; | |
Robot({ this._height, this._weight, this._names }); // ERROR: Named optional parameters can't start with an underscore | |
} | |
// this works | |
class Robot { | |
final double _height; | |
final double _weight; | |
final List<String> _names; | |
Robot({ height, weight, names }) : _height = height, _weight = weight, _names = names; | |
get height => _height; | |
get weight => _weight; | |
get names => _names; | |
} | |
// or even with default values | |
class Robot { | |
final double _height; | |
final double _weight; | |
final List<String> _names; | |
Robot({ height, weight, names }) : _height = height ?? 7, _weight = weight, _names = names; | |
get height => _height; | |
get weight => _weight; | |
get names => _names; | |
} | |
main() { | |
print(Robot().height); // 7 | |
} | |
// using public keys looks better | |
class Robot { | |
final double height; | |
final double weight; | |
final List<String> names; | |
Robot({ this.height = 7, this.weight = 100, this.names = const [] }); | |
} | |
main() { | |
print(Robot().weight); // 100 | |
} | |
// Both positional and named argument styles can be used together: | |
class Robot { | |
final double _height; | |
final double _weight; | |
final List<String> _names; | |
Robot(height, { weight, names }) : | |
_height = height, | |
_weight = weight, | |
_names = names; | |
get height => _height; | |
get weight => _weight; | |
} | |
main() { | |
var r = Robot(7, weight: 120); | |
print(r.height); // 7 | |
print(r.weight); // 120 | |
} | |
// Positional optional parameters | |
// Wrap the optional parameter with [ ] square brackets. | |
class User { | |
String name; | |
int age; | |
String home; | |
User(this.name, this.age, [this.home = 'Earth']); | |
} | |
User user1 = User('Bob', 34); | |
User user2 = User('Bob', 34, 'Mars'); | |
// Named optional parameters | |
// Wrap the optional parameter with { } curly braces. | |
class User { | |
String name; | |
int age; | |
String home; | |
User(this.name, this.age, {this.home = 'Earth'}); | |
} | |
User user1 = User('Bob', 34); | |
User user2 = User('Bob', 34, home: 'Mars'); | |
// Note | |
// If you need private fields then you can use [] square brackets: | |
class User { | |
int _id; | |
User([this._id]); | |
} | |
User user = User(3); | |
// or do as the accepted answer says and use an initializer list: | |
class User { | |
int _id; | |
User({int id}) | |
: _id = id; | |
} | |
User user = User(id: 3); |
// that's why we have named constructors | |
class Robot { | |
final double height; | |
Robot(this.height); | |
Robot.fromPlanet(String planet) : height = (planet == 'geonosis') ? 2 : 7; | |
Robot.copy(Robot other) : this(other.height); | |
} | |
// What happened in copy? We used this to call the default constructor, effectively "redirecting" the instantiation. | |
main() { | |
print(Robot.copy(Robot(7)).height); // 7 | |
print(Robot.fromPlanet('geonosis').height); // 2 | |
print(Robot.fromPlanet('earth').height); // 7 | |
} | |
// with inheritance | |
class Machine { | |
String name; | |
Machine(); | |
Machine.named(this.name); | |
} | |
class Robot extends Machine { | |
final double height; | |
Robot(this.height); | |
Robot.named({ height, name }) : this.height = height, super.named(name); | |
} | |
main() { | |
print(Robot.named(height: 7, name: "Walter").name); // Walter | |
} | |
// you may want to keep private the default constructor and expose only named ones | |
// We can make a constructor private by prefixing it with an underscore: | |
class Robot { | |
Robot._(); | |
} | |
// you can call a private constructor from a named one | |
class Machine { | |
String name; | |
Machine._(); | |
Machine.named(this.name); | |
} | |
class Robot extends Machine { | |
final double height; | |
Robot._(this.height, name) : super.named(name); | |
Robot.named({ height, name }) : this._(height, name); | |
} | |
main() { | |
print(Robot.named(height: 7, name: "Walter").name); // Walter | |
} |
// Factory constructors are syntactic sugar for the “factory pattern”, usually implemented with static functions. | |
// They appear like a constructor from the outside (useful for example to avoid breaking API contracts), | |
// but internally they can delegate instance creation invoking a “normal” constructor. | |
// This explains why factory constructors do not have initializers. | |
// Since factory constructors can return other instances (so long as they satisfy the interface of the current class), | |
// we can do very useful things like: | |
// * caching: conditionally returning existing objects (they might be expensive to create) | |
// * subclasses: returning other instances such as subclasses | |
// They work with both normal and named constructors! | |
class Robot { | |
final double height; | |
Robot._(this.height); | |
factory Robot() { | |
return Robot._(7); | |
} | |
} | |
main() { | |
print(Robot().height); // 7 | |
} | |
// Here’s our robot warehouse, that only supplies one robot per height: | |
class Robot { | |
final double height; | |
static final _cache = <double, Robot>{}; | |
Robot._(this.height); | |
factory Robot(height) { | |
return _cache[height] ??= Robot._(height); | |
} | |
} | |
main() { | |
final r1 = Robot(7); | |
final r2 = Robot(7); | |
final r3 = Robot(9); | |
print(r1.height); // 7 | |
print(r2.height); // 7 | |
print(identical(r1, r2)); // true | |
print(r3.height); // 9 | |
print(identical(r2, r3)); // false | |
} | |
// Finally, to demonstrate how a factory would instantiate subclasses, | |
// let’s create different robot brands that calculate prices as a function of height: | |
abstract class Robot { | |
factory Robot(String brand) { | |
if (brand == 'fanuc') return Fanuc(2); | |
if (brand == 'yaskawa') return Yaskawa(9); | |
if (brand == 'abb') return ABB(7); | |
throw "no brand found"; | |
} | |
double get price; | |
} | |
class Fanuc implements Robot { | |
final double height; | |
Fanuc(this.height); | |
double get price => height * 2922.21; | |
} | |
class Yaskawa implements Robot { | |
final double height; | |
Yaskawa(this.height); | |
double get price => height * 1315 + 8992; | |
} | |
class ABB implements Robot { | |
final double height; | |
ABB(this.height); | |
double get price => height * 2900 - 7000; | |
} | |
main() { | |
try { | |
print(Robot('fanuc').price); // 5844.42 | |
print(Robot('abb').price); // 13300 | |
print(Robot('flutter').price); | |
} catch (err) { | |
print(err); // no brand found | |
} | |
} | |
The factory constructor Robot(height) simply always returns the one and only instance that was created | |
when loading the Robot class. | |
class Robot { | |
static final Robot _instance = Robot._(7); | |
final double height; | |
factory Robot() { | |
return _instance; | |
} | |
Robot._(this.height); | |
} | |
main() { | |
var r1 = Robot(); | |
var r2 = Robot(); | |
print(identical(r1, r2)); // true | |
print(r1 == r2); // true | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment