Dart 提供了多種建構子型別,方便開發者根據不同情境初始化物件。工廠建構子可在傳回物件前執行額外邏輯,例如驗證或轉換資料。命名建構子則允許定義多個建構子,提升程式碼可讀性。靜態成員屬於類別本身,而非特定例項,常用於定義常數、工具方法或單例模式。理解這些建構子和靜態成員的特性,有助於編寫更具彈性且易於維護的 Dart 程式碼。此外,Dart 的空安全機制能有效避免 null pointer exceptions,提升程式碼的穩定性。可空型別以問號標記,提醒開發者注意潛在的 null 值,並使用 null-aware operators 進行安全處理,確保程式碼的健壯性。
工廠建構子(Factory Constructors)
工廠建構子是一種特殊的建構子,允許您在傳回新物件之前執行一些工作。它們使用 factory
關鍵字來定義。工廠建構子可以用來執行驗證、錯誤檢查和修改引數等工作。
以下是使用工廠建構子建立 User
物件的範例:
class User {
final int id;
final String name;
factory User.fromJson(Map<String, Object> json) {
final userId = json['id'] as int;
final userName = json['name'] as String;
return User(id: userId, name: userName);
}
User({required this.id, required this.name});
}
在這個範例中,User.fromJson
是一個工廠建構子,它從一個 JSON 物件中建立一個 User
物件。它執行一些工作來初始化 id
和 name
屬性,然後傳回一個新建立的 User
物件。
命名建構子(Named Constructors)
命名建構子是一種特殊的建構子,允許您建立多個建構子,每個建構子都有不同的名稱。命名建構子使用 :
關鍵字來定義。
以下是使用命名建構子建立 User
物件的範例:
class User {
final int id;
final String name;
User.fromJson(Map<String, Object> json)
: id = json['id'] as int,
name = json['name'] as String;
}
在這個範例中,User.fromJson
是一個命名建構子,它從一個 JSON 物件中建立一個 User
物件。
建構子比較(Constructor Comparisons)
建構子可以有很多種不同的變化,包括:
- 順向或非順向(Forwarding or non-forwarding)
- 命名或非命名(Named or unnamed)
- 生成或工廠(Generative or factory)
- 常數或非常數(Constant or not constant)
- 有引數或無引數(With parameters or without)
- 簡短形式或長形式(Short-form or long-form)
- 公開或私有(Public or private)
以下是使用不同種類的建構子建立 User
物件的範例:
class User {
final int id;
final String name;
const User(this.id, this.name); // 公開、非順向、非命名、生成、常數、有引數、簡短形式
factory User.fromJson(Map<String, Object> json) {
// ...
} // 工廠建構子
User._internal(); // 私有、非順向、命名、生成、非常數、無引數、長形式
}
挑戰(Challenges)
現在,讓我們來做一個挑戰,建立一個 Student
類別,具有 firstName
和 lastName
的最終屬性,以及 grade
的可變屬性。新增一個建構子來初始化所有屬性,並新增一個方法來格式化 Student
物件的輸出。
以下是解決方案:
class Student {
final String firstName;
final String lastName;
int grade;
Student({required this.firstName, required this.lastName, required this.grade});
String toString() {
return '$firstName $lastName: $grade';
}
}
void main() {
Student bert = Student(firstName: 'Bert', lastName: 'Ernie', grade: 95);
Student ernie = Student(firstName: 'Ernie', lastName: 'Bert', grade: 85);
print(bert);
print(ernie);
}
這個解決方案建立了一個 Student
類別,具有 firstName
、lastName
和 grade
的屬性。它增加了一個建構子來初始化所有屬性,並增加了一個 toString
方法來格式化 Student
物件的輸出。最終,它建立了兩個 Student
物件,bert
和 ernie
,並將它們的屬性輸出到主控臺。
Dart 中的靜態成員和建構函式
在 Dart 中,建構函式是用於建立類別的物件。您可以透過類別名稱加上括號來呼叫建構函式,例如 var obj = MyClass();
。Dart 還支援命名建構函式,允許您定義多個建構函式,每個都有不同的名稱。
建構函式的種類
Dart 中有兩種建構函式:無名建構函式和命名建構函式。無名建構函式的名稱與類別名稱相同,而命名建構函式則在類別名稱後面增加了額外的識別符。
class MyClass {
// 無名建構函式
MyClass() {
print('無名建構函式');
}
// 命名建構函式
MyClass.named() {
print('命名建構函式');
}
}
初始化列表
初始化列表允許您在建構函式中初始化欄位變數。這是透過在建構函式引數列表後面新增冒號和初始化列表來實作的。
class MyClass {
int _x;
int _y;
MyClass(int x, int y) : _x = x, _y = y {
print('建構函式');
}
}
常數建構函式
新增 const
標記到建構函式可以讓您建立不可變的類別例項。
class MyClass {
final int _x;
final int _y;
const MyClass(int x, int y) : _x = x, _y = y {
print('常數建構函式');
}
}
靜態成員
靜態成員是屬於類別而不是例項的變數或方法。您可以透過在成員變數或方法前面新增 static
標記來定義靜態成員。
class MyClass {
static int myProperty = 0;
static void myMethod() {
print('靜態方法');
}
}
您可以直接透過類別名稱來訪問靜態成員,而不需要建立例項。
void main() {
print(MyClass.myProperty);
MyClass.myMethod();
}
靜態變數通常用於定義常數或單例模式。
常數
您可以使用 const
標記來定義常數。
class TextStyle {
static const double _defaultFontSize = 17.0;
final double fontSize;
TextStyle({this.fontSize = _defaultFontSize});
}
這種方式可以使您的程式碼更清晰、更易於維護。
靜態成員:Color Codes 和 Singleton Pattern
在前面的例子中,我們看到了一個私有的常數,但在某些情況下,公共常數也很有用。例如,Flutter 的 Colors 類別就是一個很好的例子。現在,我們將建立一個簡化的版本。
class Colors {
static const int red = 0xFFD13F13;
static const int purple = 0xFF8107D9;
static const int blue = 0xFF1432C9;
}
這些是紅、紫和藍的十六進位制值。0x
字首告訴 Dart 我們正在使用十六進位制值來表示整數。八個十六進位制字元之後的 0x
跟隨著 AARRGGBB 模式,其中 AA 是 alpha 或透明度,RR 是紅色,GG 是綠色,BB 是藍色。
然後,在 main
中新增以下行:
final backgroundColor = Colors.purple;
這是一種更易於理解的方式來描述顏色,而不是在整個應用程式中散佈十六進位制值。
Singleton Pattern
另一個使用靜態變數的方法是建立一個單例類別。單例是一種設計模式,只有一個物件例項。雖然有些人辯論它們的好處,但它們使某些任務更加方便。
在 Dart 中建立單例很容易。你不會想要讓 User
成為單例,因為你可能會有多個不同的使用者,需要多個 User
例項。但你可能想要建立一個單例類別作為資料庫幫助器,以確保你不會開啟多個資料庫連線。
以下是基本單例類別的外觀:
class MySingleton {
MySingleton._();
static final MySingleton instance = MySingleton._();
}
MySingleton._()
部分是一個私有的、具名的建構函式。有些人喜歡稱它為 _internal
以強調它不能從外部呼叫。底線使得它不可能正常例項化類別。但靜態屬性,只初始化一次,提供了一個對例項化物件的參照。
注意:靜態欄位和頂級變數(類別外的全域性變數)是懶惰初始化的,這意味著 Dart 不會在你第一次使用它們之前計算和分配它們的值。
你可以像這樣訪問單例:
final mySingleton = MySingleton.instance;
由於工廠建構函式不需要傳回物件的新例項,因此你也可以使用工廠建構函式實作單例模式:
class MySingleton {
MySingleton._();
static final MySingleton _instance = MySingleton._();
factory MySingleton() => _instance;
}
這裡的優點是你可以隱藏它是一個單例的事實,對於誰使用它來說:
final mySingleton = MySingleton();
從外部來看,這看起來完全像一個正常的物件。這允許你稍後將其更改回生成建構函式,而不會影響專案的其他部分的程式碼。
過去兩個部分都在關於靜態變數。接下來,你將看看靜態方法。
靜態方法
你可以使用靜態方法做一些有趣的事情。靜態方法是屬於類別本身的方法,而不是例項方法。它們不需要建立類別的例項就可以被呼叫。
以下是靜態方法的例子:
class MyClass {
static void myStaticMethod() {
print('這是靜態方法');
}
}
你可以像這樣呼叫靜態方法:
MyClass.myStaticMethod();
靜態方法可以用來提供實用函式或工具函式,例如數學函式或字串處理函式。
內容解密:
- 我們建立了一個
Colors
類別,包含靜態常數來表示顏色。 - 我們使用十六進位制值來表示顏色,並使用
0x
字首來告訴 Dart 我們正在使用十六進位制值。 - 我們建立了一個單例類別
MySingleton
,使用靜態變數和私有的具名建構函式。 - 我們使用工廠建構函式來實作單例模式,允許我們隱藏它是一個單例的事實。
- 我們介紹了靜態方法的概念,展示瞭如何建立和呼叫靜態方法。
圖表翻譯:
flowchart TD A[建立 Colors 類別] --> B[定義靜態常數] B --> C[使用十六進位制值] C --> D[建立單例類別] D --> E[使用靜態變數和私有的具名建構函式] E --> F[使用工廠建構函式] F --> G[建立靜態方法] G --> H[呼叫靜態方法]
靜態方法的應用
靜態方法是與類別相關的方法,但不與任何特定的例項相關。它們通常用於建立公用方法或工具方法。以下是使用靜態方法的範例:
class Math {
static num max(num a, num b) {
return (a > b) ? a : b;
}
static num min(num a, num b) {
return (a < b) ? a : b;
}
}
您可以在 main
函式中使用這些靜態方法:
print(Math.max(2, 3)); // 3
print(Math.min(2, 3)); // 2
然而,在 Dart 中,通常更好的是將這些公用方法放在自己的檔案中作為頂級函式。您可以將這些函式放在一個名為 math.dart
的檔案中:
num max(num a, num b) {
return (a > b) ? a : b;
}
num min(num a, num b) {
return (a < b) ? a : b;
}
然後,您可以在需要使用這些函式的檔案中匯入它們:
import 'package:starter/math.dart';
void main() {
print(max(2, 3)); // 3
print(min(2, 3)); // 2
}
如果您需要避免命名衝突,您可以使用 as
關鍵字:
import 'package:starter/math.dart' as math;
void main() {
print(math.max(2, 3)); // 3
print(math.min(2, 3)); // 2
}
建立新物件
您也可以使用靜態方法建立新物件。例如,您可以使用靜態方法實作與工廠建構函式相同的功能。以下是使用靜態方法建立新物件的範例:
class User {
final int id;
final String name;
User({required this.id, required this.name});
static User fromJson(Map<String, Object> json) {
final userId = json['id'] as int;
final userName = json['name'] as String;
return User(id: userId, name: userName);
}
}
您可以使用這個靜態方法建立新物件:
void main() {
final json = {'id': 1, 'name': 'John'};
final user = User.fromJson(json);
print(user.id); // 1
print(user.name); // John
}
Dart 中的靜態成員和空安全性
在 Dart 中,靜態成員是屬於類別而非例項的屬性或方法。靜態常數可用於儲存不變的值,例如 π。單例是一個只有單一例項的類別,而工具方法是與類別相關但不屬於任何例項的方法。
靜態成員
靜態成員使用 static
關鍵字宣告。靜態屬性和方法屬於類別本身,而非例項。例如:
class Sphere {
static const double pi = 3.14159;
final double radius;
const Sphere({required this.radius});
double get volume => (4/3) * pi * radius * radius * radius;
double get surfaceArea => 4 * pi * radius * radius;
}
在上述例子中,pi
是一個靜態常數,volume
和 surfaceArea
是例項方法。
工廠建構函式
工廠建構函式是一種特殊的建構函式,傳回類別的例項或子類別的例項。工廠建構函式使用 factory
關鍵字宣告。例如:
class User {
final int id;
final String name;
const User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) {
final userId = json['id'] as int;
final userName = json['name'] as String;
return User(id: userId, name: userName);
}
}
在上述例子中,User.fromJson
是一個工廠建構函式,傳回 User
的例項。
空安全性
空安全性是一種語言特性,確保變數不為空。Dart 2.12 引入了空安全性,允許開發者選擇是否啟用空安全性。
在空安全性啟用時,變數必須明確宣告為可空或不可空。例如:
int? postalCode; // 可空
int postalCode = 12345; // 不可空
如果變數宣告為可空,則必須使用 ?
來訪問其屬性或方法。例如:
int? postalCode;
print(postalCode?.toString()); // 如果 postalCode 不為空,則列印其字串表示
如果變數宣告為不可空,則無需使用 ?
來訪問其屬性或方法。例如:
int postalCode = 12345;
print(postalCode.toString()); // 列印其字串表示
什麼是 Null?
在程式設計中,Null 是一個特殊的值,代表「沒有值」或「空值」。它是一個有用的概念,可以用來表示某個變數或屬性尚未被初始化或沒有值。
Null 的問題
雖然 Null 是一個有用的概念,但它也可能導致一些問題。開發人員可能會忘記 Null 的存在,並沒有在程式碼中處理它。這可能導致程式崩潰或出現錯誤。
Dart 中的 Null
在 Dart 中,Null 是一個物件,實際上是 Null 類別的唯一例項。這意味著 Null 是一個有值的物件,但它沒有任何方法或屬性。
Dart 中的 Null 安全性
Dart 有一個名為 Null 安全性的功能,可以幫助開發人員避免 Null 相關的錯誤。這個功能可以確保變數或屬性不會被指定為 Null,除非它們被明確宣告為可為 Null。
可為 Null 的型別
在 Dart 中,型別可以被宣告為可為 Null 或不可為 Null。可為 Null 的型別以問號(?)結尾,例如 int?
或 String?
。不可為 Null 的型別則不以問號結尾,例如 int
或 String
。
非可為 Null 的型別
非可為 Null 的型別是 Dart 中的預設型別。這意味著變數或屬性不會被指定為 Null,除非它們被明確宣告為可為 Null。
例子
以下是一些例子,展示瞭如何在 Dart 中使用可為 Null 和不可為 Null 的型別:
int myInt = 1; // 非可為 Null 的型別
int? myIntNullable = null; // 可為 Null 的型別
double myDouble = 3.14159265; // 非可為 Null 的型別
double? myDoubleNullable = null; // 可為 Null 的型別
bool myBool = true; // 非可為 Null 的型別
bool? myBoolNullable = null; // 可為 Null 的型別
String myString = 'Hello, Dart!'; // 非可為 Null 的型別
String? myStringNullable = null; // 可為 Null 的型別
Dart 中的可空型別
在 Dart 中,任何型別都可以是可空的,這意味著它可以包含 null
值。可空型別透過在型別名稱後新增一個問號 (?
) 來表示。例如,int?
是一個可空的整數型別,String?
是一個可空的字串型別。
可空型別的特點
可空型別可以包含 null
值,這意味著你可以將 null
指定給一個可空變數。例如:
int? myInt = null;
double? myDouble = null;
bool? myBool = null;
String? myString = null;
User? myUser = null;
如果你不初始化一個可空變數,它將預設為 null
。
非可空型別與可空型別的關係
每個非可空型別都有一個對應的可空型別。例如,int
和 int?
、bool
和 bool?
、User
和 User?
。非可空型別是其可空型別的子型別,這意味著你可以將一個非可空值指定給一個可空變數。
處理可空型別
Dart 現在要求你必須處理可空型別的 null
值。你不能直接使用一個可空變數,直到你處理了 null
的可能性。例如:
String? name;
print(name); // 錯誤:不能使用可空變數
要處理可空型別,你需要使用 null-aware 運算子,例如 ??
或 ?.
。
null-aware 運算子
??
運算子用於提供一個預設值,當可空變數為 null
時使用。例如:
String? name;
String fullName = name ?? 'Unknown';
print(fullName); // 輸出:Unknown
?.
運算子用於訪問可空變數的屬性或方法,當可空變數為 null
時傳回 null
。例如:
String? name;
String? uppercaseName = name?.toUpperCase();
print(uppercaseName); // 輸出:null
在 Dart 中,使用可空型別可以幫助你避免 null
值相關的錯誤。透過使用 null-aware 運算子,你可以安全地處理可空型別的 null
值。
程式碼示例
void main() {
int? age;
double? height;
String? message;
print(age); // 輸出:null
print(height); // 輸出:null
print(message); // 輸出:null
String? profession;
profession = 'basketball player';
print(profession); // 輸出:basketball player
const iLove = 'Dart';
print(iLove); // 輸出:Dart
}
圖表翻譯
graph LR A[可空型別] -->|包含|> B[null 值] A -->|不包含|> C[非 null 值] B -->|處理|> D[null-aware 運算子] C -->|使用|> E[變數值]
內容解密
在 Dart 中,使用可空型別可以幫助你避免 null
值相關的錯誤。透過使用 null-aware 運算子,你可以安全地處理可空型別的 null
值。可空型別可以包含 null
值,這意味著你可以將 null
指定給一個可空變數。非可空型別是其可空型別的子型別,這意味著你可以將一個非可空值指定給一個可空變數。
Dart 中的 Nullability
Dart 是一種靜態型別語言,意味著它可以在編譯時期就檢查出可能的錯誤。其中一個重要的功能是 nullability,它可以幫助你避免 null pointer exceptions。
Type Promotion
Dart 的編譯器可以自動推斷變數的型別,甚至可以將 nullable 型別提升為 non-nullable 型別。例如:
String? name;
name = 'Ray';
print(name.length);
在這個例子中,name
的型別是 String?
,但因為它被指定為一個非 null 的字串,所以 Dart 可以自動將它提升為 String
型別。
Flow Analysis
Dart 的編譯器使用流分析(flow analysis)來檢查程式碼的每一個可能的路徑。如果沒有任何路徑會導致 null,則可以將 nullable 型別提升為 non-nullable 型別。例如:
bool isPositive(int? anInteger) {
if (anInteger == null) {
return false;
}
return !anInteger.isNegative;
}
在這個例子中,anInteger
的型別是 int?
,但因為它已經被檢查為非 null,所以可以將它提升為 int
型別。
Null-Aware Operators
Dart 提供了一系列的 null-aware operators,可以幫助你處理可能為 null 的值。其中包括:
??
:如果 null,則使用右邊的值。??=
:如果 null,則指定為右邊的值。?.
:如果 null,則不呼叫方法或存取屬性。?.
:如果 null,則不呼叫方法。!
:斷言值不為 null。?..
:如果 null,則不呼叫方法或存取屬性。?[]
:如果 null,則不存取陣列元素。...?
:如果 null,則不展開陣列。
從程式碼設計和維護的視角來看,Dart 的建構子和靜態成員提供強大的機制來管理物件的建立和使用。深入分析命名建構子、工廠建構子、初始化列表和常數建構子,可以發現它們在提升程式碼可讀性、靈活性和安全性方面扮演著關鍵角色。 Dart 的空安全特性更進一步強化了這一點,透過可空型別和 null-aware 運算子,有效降低了 null pointer exceptions 的風險,提升了程式碼的穩定性。
分析 Dart 的型別提升和流分析機制,我們可以發現,Dart 編譯器能智慧地判斷變數的 nullability,並在安全的情況下自動將可空型別提升為非可空型別,這不僅簡化了程式碼的編寫,也提升了執行效率。同時,靜態成員和方法的應用,例如單例模式和工具方法的建立,有效地組織了程式碼結構,提升了程式碼的可重用性。然而,需要注意的是,過度使用單例模式可能會增加程式碼的耦合性,因此需要謹慎使用。
展望未來,隨著 Dart 生態系統的持續發展,預計會有更多工具和最佳實務出現,以進一步提升程式碼的品質和開發效率。 Dart 的空安全特性和型別推斷機制將持續演進,提供更強大的型別安全保障。同時,社群也將持續探索靜態成員和建構子的最佳實踐,以更好地利用這些特性構建更健壯、更易維護的應用程式。
玄貓認為,深入理解 Dart 的建構子和靜態成員,並結合空安全特性,是每個 Dart 開發者進階的必經之路。掌握這些核心概念,不僅能寫出更優雅、更安全的程式碼,也能提升整體的軟體設計能力。對於追求程式碼品質的開發者而言,持續學習和應用 Dart 的新特性和最佳實踐至關重要。