# [Constructors](https://dart.dev/language/constructors)
建構子是種特殊函數, 可以初始化物件變數並產生實例
### 建構子種類
* **Generative constructors**
名稱和class名稱相同, 可以初始化物件變數
* **Default constructors**
沒有寫建構子時, 系統隱式添加的建構子
* **Named constructors**
ClassName.identifier, identifier可以說明該建構子的特性 例如 className.fromJson()
* **Constant constructors**
建立實例作為編譯時期常數
* **Factory constructors**
建立子類型的新實例或從快取傳回現有實例
* **Redirecting constructor**
將呼叫轉送到同一類別中的另一個建構子
---
### Generative constructors
```
class Point {
// Initializer list of variables and values
double x = 2.0;
double y = 2.0;
// Generative constructor with initializing formal parameters:
Point(this.x, this.y);
}
```
### Default constructors
如果不聲明建構函數,Dart 將使用預設建構函數。預設建構子是沒有參數not named constructors
### Named constructors
有特殊目的的建構子或使class可以有**多個建構子**
子類別不會繼承超類別的命名建構函數。若要建立具有超類別中定義的命名建構函式的子類,請在子類別中實作該建構函式。
```
class Point {
int x;
int y;
// 一般建構子
Point(this.x, this.y);
// 座標在原點的建構子
Point.origin()
: x = 0,
y = 0;
}
```
### Constant constructors
希望產生一個編譯時期就不會變化的物件, 則把所有物件變數設定成 **final**, 並用 **const** 修飾返回的物件
```
class NeverChangePoint {
static const origin=NeverChangePoint(0, 0);
final x;
final y;
const NeverChangePoint(this.x, this.y);
void repr()=>print('($x, $y)');
}
class Point{
int x;
int y;
Point(this.x,this.y);
void repr()=>print('($x, $y)');
}
void main() {
var no=NeverChangePoint.origin;
no.repr();
// no.x=23; // The setter 'x' isn't defined for the class 'NeverChangePoint'.
var np=NeverChangePoint(23, 12);
np.repr(); // (23,12)
// np.x=46; // The setter 'x' isn't defined for the class 'NeverChangePoint'.
// var p=const Point(45, 66); // 不是const constructor, 所以不能用const修飾
}
```
### Redirecting constructors
重定向到同一類別中的另一個構造函數。重定向構造函數有一個空的主體。建構子用this來代替冒號 (:) 後面的類別名稱。
```
class Point {
double x, y;
// 生成建構子
Point(this.x, this.y);
// 委託生成建構子來處理
Point.alongXAxis(double x) : this(x, 0);
// 這樣也可以
Point.alongYAxis(double y)
: x = 0,
y = y;
void repr() => print('($x, $y)');
}
void main() {
var px = Point.alongXAxis(23);
px.repr();
var py=Point.alongYAxis(23);
py.repr();
}
```
### factory constructors
一個class A的factory constructors就是一個一般的函數, 本身不會產生新的實例物件, 所以一定要有`return`.
factory應該解釋成倉庫, 它return已經存在的物件obj, 且(obj is A)為真, 所以obj可以是:
* 快取裡面已經存在的obj2, 且(obj2 is A)為真
* 用class A的另外的建構函數產生的實例
* 用class A的subClass產生的實例
[參考](https://stackoverflow.com/questions/53886304/understanding-factory-constructor-code-example-dart)
```
class Logger {
// 用類別變數儲存做過的Logger, 相同的account始終只有一個對應的Logger物件
static final _cache = <String, Logger>{}; // _cache={account:Logger}
// 實例儲主要存的資料
final String account;
String password;
// 會產生全新的Logger物件, 私有命名建構函數, 外界不能叫用
Logger._new(String acc, String pw)
: account = acc,
password = pw;
// 外界主要是叫用這個
factory Logger(String acc, String pw) {
// 若key acc不存在, 則先新增: _cache[acc]=Logger._new(acc, pw), 並返回_cache[acc]
// 若key acc存在, 則返回 _cache[acc]
final LoggerObj = _cache.putIfAbsent(acc, () => Logger._new(acc, pw));
// 更新密碼
if (!(LoggerObj.password == pw)) {
LoggerObj.password = pw;
_cache[acc] = LoggerObj;
}
// return的實例一定是class 或 subclass的實例
return LoggerObj;
}
// 實例方法
String info() => 'Account: $account, Password: $password';
}
void main() {
var bob1 = Logger('Bob Smith', '111111');
print('bob1 ${bob1.info()}');
var bob2 = Logger('Bob Smith', '222222');
print('bob2 ${bob2.info()}');
assert(identical(bob1, bob2)); // 相同的Logger
print('bob1 ${bob1.info()}');
var mary = Logger('Mary Jones', '333333');
print('mary ${mary.info()}');
assert(!identical(bob2, mary)); // 不同的Logger
print(Logger._cache);
}
```
### Constructor tear-offs (撕掉括號)
tear-offs就是用函數的名稱來代表此函數物件,可以把這個物件指定給變數,當作另一個函數的參數等等.
**constructor.new** 是用來引用類別中未命名構造函數的一種簡語法糖。它將構造函數作為一等對象來使用,允許我們將構造函數賦值給變量、傳遞給函數,或在其他地方靈活使用。也可以用className.identifier來指定class的命名構造函數.
```
class User {
final String name;
User(this.name); // construct.new refers to
}
void createObjects(List<String> names, Function(String) constructor) {
var objects = names.map(constructor).toList();
objects.forEach((obj) => print(obj.name));
}
void main() {
var names = ['Alice', 'Bob', 'Charlie'];
// User.new可以參考到User的預設unnamed generative constructor
// User.new就是一個constructor tear-offs
createObjects(names, User.new);
// Output:
// Alice
// Bob
// Charlie
}
```
```
class Shape {
String type;
Shape.circle() : type = 'circle';
Shape.square():type='square';
}
void main() {
// use tear-offs to assign
// named constructor function to a variable
Function createCircle=Shape.circle;
Function createSquare=Shape.square;
Shape circle=createCircle();
print(circle.type);
Shape square=createSquare();
print(square.type);
}
```
### 實例變數初始化
1. Initialize instance variables in the declaration
```
class PointA {
double x = 1.0;
double y = 2.0;
}
```
2. Use initializing formal parameters (this.parameterName)
為了簡化將建構函式參數指派給實例變數的常見模式,Dart 具有初始化形式參數。
某些建構函式和建構函式的某些部分無法存取this:
* Factory constructors
* 初始化清單的右側
* 父類別建構子的參數
```
class PointB {
final double x;
final double y;
PointB(this.x, this.y);
PointB.optional([this.x=0.0, this.y = 0.0]);
PointB.re({required this.x,required this.y});
PointB.na({this.x=0.0,this.y=0.0});
}
```
3. 構造函數參數可以設定為可為空並且不進行初始化。
```
class PointD {
double? x; // null if not set in constructor
double? y; // null if not set in constructor
}
void main() {
assert(PointD() is PointD);
}
```
4. 使用初始化列表(initializer list)
```
// 在建構函式主體運行之前,可以用初始化列表初始化實例變數
class Point {
final double x;
final double y;
Point.fromJson(Map json)
: x = json['x'],
y = json['y'] {
print('in Point.fromJson, x=$x, y=$y');
}
}
```
```
// 驗證實例參數也可以使用initializer list
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
```
```
// 使用initializer list初始化所有final field
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
```
### 建構子的繼承
Dart 不會自動繼承父類的建構子。取而代之的是,開發者需要顯式地呼叫父類的建構子,並根據需要進行重新定義或使用語法糖(如 super 或 : this())來簡化操作。
默認的建構子:非命名且無參數的建構子
1. 執行子類默認建構子=父類默認建構子+子類默認建構子
```
class Parent {
Parent() {
print('Parent constructor called');
}
}
class Child extends Parent {
Child() {
print('Child constructor called');
}
}
// 執行child = Child();時, 都會先執行Parent(); 再執行Child()
void main() {
var child = Child();
// 輸出:
// Parent constructor called
// Child constructor called
}
```
2. 要顯式的呼叫父類的 "非默認建構子"(named or 有參數建構子)
```
class Person {
final String name;
final int age;
Person(this.name, this.age) {
print('In Person, name:$name, age:$age');
}
Person.named(this.name, this.age);
Person.requiredNamed({required String name, int age = 0})
: this.name = name,
this.age = age;
Person.formalArgs(String name, int age)
: this.name = name,
this.age = age;
Person.defaultValue()
: name = 'unknown',
age = 100;
void printInfo() => print('name:$name, age:$age');
}
class Student extends Person {
final String id;
// initializing formal parameters constuctor
Student(String name, int age, this.id) : super(name, age);
// 已set好的 constructor, super.defaultValue()要放在最後
Student.setAlready()
: id = 'A000000',
super.defaultValue();
// named constructor
Student.withName1(String name, this.id) : super.requiredNamed(name: name);
Student.withName2(String name, int age, this.id)
: super.requiredNamed(name: name, age: age) {
print('name:$name, age:$age, id:$id');
}
// named private constructor
Student._internal(String name, int age, this.id) : super(name, age);
// factory constructor
factory Student.fac(String name, int age, String id) {
// 可以添加自己的業務邏輯
if (age >= 18) {
print('is adult, name:$name, age:$age, id:$id');
} else {
print('is teen, name:$name, age:$age, id:$id');
}
return Student._internal(name, age, id);
}
@override
void printInfo() => print('name:$name, age:$age, id:$id');
}
void main() {
Student mary = Student('Mary Jones', 21, 'A123456');
mary.printInfo(); // name:MarStudent._internal(name, age, id);y Jones, age:21, id:A123456
Student stranger = Student.setAlready();
stranger.printInfo(); // name:unknown, age:100, id:A000000
Student bob = Student.withName1('Bob', "A456123");
bob.printInfo(); // name:Bob, age:0, id:A456123
Student steve = Student.withName2(
'Steve', 19, 'N123456'); // name:Steve, age:19, id:N123456
Student jay = Student.fac(
'Jay', 23, 'H123456'); // is adult, name:Jay, age:23, id:H123456
}
```
3. Super parameters
super parameters 是一種語法糖,用於將子類建構子的參數直接傳遞給父類建構子。Dart 2.17 後才引入, super.x在語法和語意上與this.x相似
```
class Person {
String name;
Person(this.name); // A convenient constructor
}
class Student extends Person{
int age;
// 原本寫法
// Student(String name, this.age):super(name);
// 語法糖
Student(super.name, this.age);
}
```
```
// named args
class Parent {
final String name;
Parent({required this.name});
}
class Child extends Parent {
final int age;
Child({required super.name, required this.age});
}
```
```
class Vector2d {
final double x;
final double y;
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
final double z;
//原本寫法
// Vector3d.yzPlane({required double y, required this.z})
// : super.named(x: 0, y: y);
// Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
Vector3d.yzPlane({required super.x,required super.y, required this.z}):super.named();
}
```