---
# System prepended metadata

title: Constructors
tags: [OOP]

---

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

