Dart 提供了多種建構函式型別,例如預設建構函式、命名建構函式、轉發建構函式以及工廠建構函式,方便開發者根據不同情境初始化物件。初始化清單則確保物件屬性在建構過程中被正確設定,避免外部修改。使用命名引數能提高程式碼可讀性,降低引數順序錯誤的風險。配合 final 關鍵字,可以建立不可變物件,提升程式碼的穩定性。此外,Dart 的私有變數機制結合庫的概念,有效實作類別封裝,隱藏內部細節,降低程式碼耦合度。

類別的屬性和方法

類別的屬性可以是公有的,也可以是私有的。公有的屬性可以被外部存取,私有的屬性只能在類別內部存取。方法也可以是公有的或私有的。

class Email {
  String _value = '';

  String get value => _value;

  set value(String newValue) {
    if (newValue.isEmpty) {
      throw ArgumentError('Email cannot be empty');
    }
    _value = newValue;
  }
}

在上面的例子中,Email 類別有一個私有的 _value 屬性和一個公有的 value 屬性。value 屬性有 getter 和 setter 方法,getter 方法傳回 _value 的值,setter 方法設定 _value 的值,並且檢查新值是否為空。

建構式

建構式是類別的一種特殊方法,用於建立類別的例項。建構式的名稱與類別的名稱相同,且沒有傳回型別。

class User {
  int id;
  String name;

  User(this.id, this.name);
}

在上面的例子中,User 類別有一個建構式,該建構式接受兩個引數 idname,並且將這些引數指定給類別的屬性。

預設建構式

如果類別沒有定義建構式,Dart 會提供一個預設建構式。預設建構式不接受任何引數,且只會傳回類別的例項。

class Address {
  var value = '';
}

等同於

class Address {
  Address();
  var value = '';
}

產生式建構式

產生式建構式是類別的一種特殊建構式,用於直接生成類別的例項。

class User {
  int id;
  String name;

  User(this.id, this.name);
}

命名建構式

命名建構式是類別的一種特殊建構式,用於生成類別的例項,並且可以指定建構式的名稱。

class User {
  int id;
  String name;

  User(this.id, this.name);

  User.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        name = json['name'];
}

在上面的例子中,User 類別有兩個建構式,一個是預設建構式,另一個是命名建構式 fromJson,該建構式用於從 JSON 物件生成 User 例項。

轉發建構式

轉發建構式是類別的一種特殊建構式,用於將建構式的引數轉發給其他建構式。

class User {
  int id;
  String name;

  User(this.id, this.name);

  User.fromJson(Map<String, dynamic> json) : this(json['id'], json['name']);
}

在上面的例子中,User 類別有兩個建構式,一個是預設建構式,另一個是命名建構式 fromJson,該建構式將引數轉發給預設建構式。

工廠建構式

工廠建構式是類別的一種特殊建構式,用於傳回類別的例項,但不一定是新的例項。

class User {
  int id;
  String name;

  factory User.fromJson(Map<String, dynamic> json) {
    if (json['id'] == 1) {
      return User(1, 'John');
    } else {
      return User(json['id'], json['name']);
    }
  }
}

在上面的例子中,User 類別有一個工廠建構式 fromJson,該建構式傳回 User 例項,但如果 id 為 1,則傳回一個固定的 User 例項。

  • 類別的屬性和方法可以是公有的或私有的。
  • 建構式是類別的一種特殊方法,用於建立類別的例項。
  • 產生式建構式是類別的一種特殊建構式,用於直接生成類別的例項。
  • 命名建構式是類別的一種特殊建構式,用於生成類別的例項,並且可以指定建構式的名稱。
  • 轉發建構式是類別的一種特殊建構式,用於將建構式的引數轉發給其他建構式。
  • 工廠建構式是類別的一種特殊建構式,用於傳回類別的例項,但不一定是新的例項。
  classDiagram
  class User {
    -int id
    -String name
    +User(int, String)
    +User.fromJson(Map~String, dynamic~)
  }
  class Address {
    -var value
    +Address()
  }
  class Email {
    -String _value
    +String get value()
    +set value(String)
  }

Dart 中的建構子

在 Dart 中,建構子是一種特殊的方法,用於初始化物件的屬性。下面,我們將探討 Dart 中的建構子,包括長形式建構子和短形式建構子。

長形式建構子

長形式建構子是一種傳統的建構子,需要定義一個方法體。以下是長形式建構子的範例:

class User {
  int id;
  String name;

  User(int id, String name) {
    this.id = id;
    this.name = name;
  }
}

在這個範例中,User 類別有兩個屬性:idname。長形式建構子 User(int id, String name) 用於初始化這兩個屬性。

短形式建構子

短形式建構子是一種簡潔的建構子,無需定義方法體。以下是短形式建構子的範例:

class User {
  int id;
  String name;

  User(this.id, this.name);
}

在這個範例中,短形式建構子 User(this.id, this.name) 用於初始化 idname 屬性。

使用建構子

無論是長形式建構子還是短形式建構子,都可以用來建立物件。以下是建立 User 物件的範例:

final user = User(42, 'Ray');
print(user);

在這個範例中,建立了一個 User 物件,id 屬性為 42,name 屬性為 ‘Ray’。

this 關鍵字

在建構子中,this 關鍵字用於指代當前物件。以下是使用 this 關鍵字的範例:

class User {
  int id;
  String name;

  User(int id, String name) {
    this.id = id;
    this.name = name;
  }
}

在這個範例中,this.idthis.name 用於初始化 idname 屬性。

Dart 的建構子(Constructors)

Dart 的建構子是一種特殊的方法,用於初始化物件的屬性。它的名稱與類別名稱相同,且沒有傳回型別。

預設建構子

Dart 會自動為每個類別提供一個預設建構子,如果你沒有明確定義建構子。預設建構子不需要任何引數。

自訂建構子

你可以自訂建構子來初始化物件的屬性。例如:

class User {
  int id;
  String name;

  User(this.id, this.name);
}

在這個例子中,User 類別有兩個屬性:idname。建構子 User 初始化這兩個屬性。

簡短建構子

Dart 也提供了一種簡短建構子的語法,可以用來初始化屬性。例如:

class User {
  int id = 0;
  String name = '';

  User(this.id, this.name);
}

這種語法可以讓你更簡單地初始化屬性。

命名建構子

Dart 也提供了一種命名建構子的語法,可以用來建立多個建構子。例如:

class User {
  int id;
  String name;

  User(this.id, this.name);

  User.anonymous() {
    id = 0;
    name = 'anonymous';
  }
}

在這個例子中,User 類別有兩個建構子:UserUser.anonymousUser.anonymous 建構子初始化 idname 屬性為預設值。

呼叫建構子

你可以在 main 函式中呼叫建構子來建立物件。例如:

void main() {
  User user = User(1, 'John');
  User anonymousUser = User.anonymous();
}

在這個例子中,user 物件是使用 User 建構子建立的,anonymousUser 物件是使用 User.anonymous 建構子建立的。

  • User 類別有兩個屬性:idname
  • User 建構子初始化這兩個屬性。
  • User.anonymous 建構子初始化 idname 屬性為預設值。
  • 你可以在 main 函式中呼叫建構子來建立物件。
  flowchart TD
    A[建立 User 物件] --> B[初始化 id 和 name 屬性]
    B --> C[呼叫 User 建構子]
    C --> D[建立物件]
    A --> E[呼叫 User.anonymous 建構子]
    E --> F[初始化 id 和 name 屬性為預設值]
    F --> D

這個圖表展示了建立 User 物件的流程,包括呼叫 User 建構子和 User.anonymous 建構子。

Dart 中的建構函式

Dart 的建構函式(Constructors)是一種特殊的函式,用於初始化物件的屬性。每個類別都有一個預設的建構函式,如果你沒有定義自己的建構函式,Dart 會自動提供一個。

預設建構函式

如果你沒有定義自己的建構函式,Dart 會自動提供一個預設的建構函式。這個預設建構函式不需要任何引數。

class User {
  int id;
  String name;

  User() {
    id = 0;
    name = '';
  }
}

命名建構函式

你也可以定義自己的命名建構函式。命名建構函式的名稱與類別名稱相同,但可以有不同的引數。

class User {
  int id;
  String name;

  User.anonymous() : this(0, 'anonymous');
}

在這個例子中,User.anonymous() 是一個命名建構函式,它會呼叫預設建構函式 User() 並傳遞引數 0'anonymous'

轉發建構函式

轉發建構函式是一種簡單的建構函式,它會呼叫另一個建構函式。

class User {
  int id;
  String name;

  User(this.id, this.name);

  User.anonymous() : this(0, 'anonymous');
}

在這個例子中,User.anonymous() 是一個轉發建構函式,它會呼叫 User() 並傳遞引數 0'anonymous'

選擇性和命名引數

建構函式的引數也可以是選擇性或命名的。

class MyClass {
  int myProperty;

  MyClass([this.myProperty]);

  MyClass({this.myProperty});

  MyClass({required this.myProperty});
}

在這個例子中,MyClass() 有三個不同的建構函式,每個都有不同的引數。

User 類別的命名引數

你可以為 User 類別新增命名引數。

class User {
  int id;
  String name;

  User({required this.id, required this.name});

  User.anonymous() : this(id: 0, name: 'anonymous');
}

在這個例子中,User() 有一個命名引數 idname,而 User.anonymous() 是一個轉發建構函式,它會呼叫 User() 並傳遞引數 idname

上述程式碼展示瞭如何在 Dart 中定義建構函式、命名建構函式和轉發建構函式。它也展示瞭如何使用選擇性和命名引數。這些功能可以幫助你建立更加靈活和易於使用的類別。

  flowchart TD
    A[User 類別] --> B[建構函式]
    B --> C[命名建構函式]
    C --> D[轉發建構函式]
    D --> E[選擇性和命名引數]

這個圖表展示了 User 類別的建構函式、命名建構函式和轉發建構函式之間的關係。它也展示了選擇性和命名引數的使用。

Dart 中的建構函式和初始化清單

在 Dart 中,建構函式是用來初始化物件的特殊函式。它們的名稱與類別名稱相同,且不需要 return 陳述式。建構函式可以用來設定物件的初始狀態。

使用命名引數

在上面的例子中,我們可以使用命名引數來提高建構函式的可讀性。例如:

class User {
  int id;
  String name;

  User({this.id = 0, this.name = 'anonymous'});
}

這樣,我們就可以使用命名引數來建立物件:

final user = User(id: 42, name: 'Ray');

這樣的寫法更為清晰,避免了因為引數順序而引起的混淆。

初始化清單

但是,還有一個問題需要解決。現在,物件的屬性可以在建構函式之外被修改:

final vicki = User(id: 24, name: 'Vicki');
vicki.name = 'Nefarious Hacker';
print(vicki);

這可能不是我們想要的行為。為了避免這種情況,我們可以使用初始化清單(initializer list)來設定物件的屬性。初始化清單是一個特殊的區塊,位於建構函式的引數列表之後,使用冒號(:)分隔。例如:

class User {
  final int id;
  final String name;

  User({this.id = 0, this.name = 'anonymous'}) : id = id, name = name;
}

在這個例子中,idname 屬性被設定為 final,這意味著它們不能在建構函式之外被修改。初始化清單則被用來設定這些屬性的初始值。

命名建構函式

我們也可以定義命名建構函式(named constructor)來建立物件。例如:

class User {
  final int id;
  final String name;

  User({this.id = 0, this.name = 'anonymous'}) : id = id, name = name;

  User.anonymous() : this();
}

這樣,我們就可以使用命名建構函式來建立物件:

final anonymousUser = User.anonymous();

這樣的寫法更為清晰,避免了因為引數順序而引起的混淆。

完整的例子

以下是完整的例子:

void main() {
  final user = User(id: 42, name: 'Ray');
  print(user);

  final anonymousUser = User.anonymous();
  print(anonymousUser);
}

class User {
  final int id;
  final String name;

  User({this.id = 0, this.name = 'anonymous'}) : id = id, name = name;

  User.anonymous() : this();
}

這個例子展示瞭如何使用命名引數、初始化清單和命名建構函式來建立物件。

私有變數與類別封裝

在 Dart 中,使用底線(_)作為變數或方法的字首,可以使其成為私有變數或方法。然而,這種私有機制是根據庫(library)的,而不是根據類別(class)。這意味著,如果類別和使用其私有變數的程式碼位於同一個檔案中,則這些私有變數仍然可以被存取。

私有變數的使用

在以下的 User 類別中,我們將 idname 屬性改為私有變數 _id_name

class User {
  int _id;
  String _name;

  User({int id = 0, String name = 'anonymous'})
      : _id = id,
        _name = name;

  // ...
}

私有變數的存取

由於私有變數是根據庫的,因此如果我們在同一個檔案中嘗試存取這些私有變數,仍然可以存取:

void main() {
  final vicki = User(id: 24, name: 'Vicki');
  vicki._name = 'Nefarious Hacker'; // 可以存取
}

封裝與庫的概念

要真正實作封裝,需要將類別和使用其私有變數的程式碼分開到不同的檔案中。這樣,私有變數就只能在定義它們的檔案中存取。

實作封裝

首先,建立一個新的檔案 user.dart,並將 User 類別移到這個檔案中:

// user.dart
class User {
  int _id;
  String _name;

  User({int id = 0, String name = 'anonymous'})
      : _id = id,
        _name = name;

  // ...
}

然後,在 main 函式所在的檔案中(例如 starter.dart),匯入 user.dart 檔案:

// starter.dart
import 'package:starter/user.dart';

void main() {
  final vicki = User(id: 24, name: 'Vicki');
  // vicki._name = 'Nefarious Hacker'; // 現在無法存取
}

這樣,私有變數 _name 就只能在 user.dart 檔案中存取,達到封裝的效果。

  classDiagram
  User *-- User
  class User {
    -int _id
    -String _name
    +User(int id, String name)
  }

在這個類別圖中,User 類別具有私有變數 _id_name,以及一個建構函式 User。私有變數只能在 User 類別中存取,達到封裝的效果。

不可變的建構子

在 Dart 中,你可以使用 finalconst 來使變數不可變。然而,在這裡,由於編譯器在執行時才會知道屬性的值,所以你只能使用 final

物件的不可變性

要使物件的屬性不可變,你可以在屬性宣告前新增 final。例如,在 User 類別中,你可以這樣做:

class User {
  final String _name;
  final int _id;

  // ...
}

這樣, _name_id 只能在物件建立時被指定,一旦物件被建立,它們就不能被改變。

類別的不可變性

如果一個類別的所有欄位都是 final,你可以在建構子前新增 const 來使類別的例項在編譯時就成為常數。例如:

class User {
  final String _name;
  final int _id;

  const User({int id = 0, String name = 'anonymous'})
      : _id = id,
        _name = name;

  const User.anonymous() : this();
}

這樣,你就可以像這樣宣告 User 物件:

const user = User(id: 42, name: 'Ray');
const anonymousUser = User.anonymous();

使用 const 的好處

使用 const 不僅可以使物件不可變,還可以使它們成為編譯時的常數。這意味著,無論你在程式中建立多少個相同屬性的物件,Dart 都只會看到一個例項。這可以節省記憶體和提高效能。

練習

給定以下類別:

class PhoneNumber {
  String value = '';
}

使 value 成為 final 變數,但不使它私有。然後,新增一個 const 建構子作為初始化 PhoneNumber 物件的唯一方式。

工廠建構子

Dart 還提供了一種叫做工廠建構子的建構子。工廠建構子可以傳回現有的類別例項或子類別的例項。這對於隱藏類別的實作細節很有用。

factory User.ray() {
  return User(id: 42, name: 'Ray');
}

工廠建構子使用一般建構子建立和傳回一個新的 User 例項。你也可以使用命名建構子來實作相同的功能。工廠建構子還可以用來建立 fromJson 方法。

建構子(Constructors)深入探討

在 Dart 中,建構子是一種特殊的方法,用於初始化物件的屬性。它們在物件被建立時被呼叫,允許您設定初始狀態。建構子可以是命名或非命名的,且可以有引數或沒有引數。

從程式碼實作到設計理念的全面檢視顯示,Dart 的建構子機制提供豐富的彈性與控制力,賦予開發者精細化物件初始化的能力。深入剖析建構子的各種型別,包含預設建構子、命名建構子、轉發建構子以及工廠建構子,可以發現它們各自的功能定位和應用場景,滿足不同情境的初始化需求。

透過多維比較分析,我們發現命名建構子有效解決了單一類別需要多種初始化方式的困境,而轉發建構子則簡化了建構子之間的程式碼重複,提升了程式碼的可維護性。此外,工廠建構子更進一步提升了物件建立的靈活性,允許傳回現有例項或子類別例項,實作更複雜的物件管理策略。然而,工廠建構子也存在一定的複雜性,需要謹慎使用以避免引入額外的維護成本。

從技術演進的角度來看,Dart 的建構子機制與不可變性概念的結合,體現了現代軟體開發對資料完整性和程式碼可靠性的追求。const 建構子的引入,更進一步提升了效能和資源利用率。預見未來,隨著 Dart 語言的持續發展,建構子機制將持續演進,以更好地支援更複雜的應用場景和程式設計正規化。

對於追求程式碼品質和可維護性的開發者而言,深入理解和靈活運用 Dart 的建構子機制至關重要。善用命名引數、初始化清單以及不同型別的建構子,將有效提升程式碼的可讀性、可維護性和效能。玄貓認為,掌握這些技巧,將使開發者在 Dart 程式設計的道路上更加遊刃有餘。