# [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(); } ```