Dart 作為一種現代化的程式語言,其函式和類別設計簡潔而強大。理解如何有效運用這些特性,是編寫優質 Dart 程式碼的關鍵。本文將帶您深入瞭解 Dart 函式和類別的定義、用法及最佳實務,並輔以程式碼範例和圖表說明,助您編寫更簡潔、易維護的 Dart 程式碼。從命名引數和預設值的使用,到箭頭函式的簡潔語法,再到 Getter 和 Setter 的封裝技巧,本文將逐步引導您掌握 Dart 函式和類別的核心概念。同時,我們也將探討物件導向程式設計中的重要原則,如封裝、繼承和多型,以及如何在 Dart 中有效運用這些原則。
函式的定義
在 Dart 中,函式的定義使用 bool
、int
、String
等關鍵字來指定傳回值的型別。函式的名稱和引數列表之間使用括號 ()
分隔。以下是個簡單的函式定義例子:
bool withinTolerance(int value, [int min = 0, int max = 10]) {
return min <= value && value <= max;
}
在這個例子中,withinTolerance
函式接受一個 int
型別的 value
引數和兩個可選的 int
型別的 min
和 max
引數。函式的傳回值是 bool
型別。
函式的呼叫
函式的呼叫使用函式名稱和引數列表。以下是呼叫 withinTolerance
函式的例子:
withinTolerance(5); // true
withinTolerance(15); // false
在這個例子中,withinTolerance
函式被呼叫兩次,第一次傳入 5
,第二次傳入 15
。函式的傳回值分別是 true
和 false
。
命名引數
Dart 中的命名引數可以使函式的呼叫更加清晰和易於理解。以下是使用命名引數的例子:
bool withinTolerance(int value, {int min = 0, int max = 10}) {
return min <= value && value <= max;
}
withinTolerance(9, min: 7, max: 11); // true
withinTolerance(9, max: 11, min: 7); // true
在這個例子中,withinTolerance
函式的 min
和 max
引數被定義為命名引數。函式的呼叫使用引數名稱來指定引數的值。
內容解密:
在這個例子中,withinTolerance
函式的定義使用了命名引數。這使得函式的呼叫更加清晰和易於理解。函式的傳回值是 bool
型別,表示 value
是否在 min
和 max
之間。
圖表翻譯:
flowchart TD A[開始] --> B[呼叫 withinTolerance 函式] B --> C[傳入 value、min 和 max 引數] C --> D[計算 min <= value && value <= max] D --> E[傳回結果] E --> F[結束]
在這個圖表中,withinTolerance
函式的呼叫和執行過程被視覺化地呈現出來。圖表顯示了函式的呼叫、引數的傳入、計算的執行和結果的傳回。
Dart 中的命名引數和預設值
在 Dart 中,函式的引數可以是命名引數,也可以是位置引數。命名引數是透過名稱來指定的,而位置引數是透過位置來指定的。
命名引數
命名引數是透過名稱來指定的,例如:
void withinTolerance({int value, int min = 0, int max = 10}) {
// ...
}
在這個例子中,value
、min
和 max
都是命名引數。命名引數可以是可選的,也可以是必需的。
###預設值 命名引數可以有預設值,例如:
void withinTolerance({int value, int min = 0, int max = 10}) {
// ...
}
在這個例子中,min
和 max
的預設值分別是 0 和 10。如果沒有指定 min
和 max
的值,則使用預設值。
必需引數
如果一個命名引數是必需的,可以使用 required
關鍵字來指定,例如:
bool withinTolerance({
required int value,
int min = 0,
int max = 10,
}) {
return min <= value && value <= max;
}
在這個例子中,value
是必需的引數,如果沒有指定 value
的值,則會報錯。
練習
- 寫一個名為
youAreWonderful
的函式,該函式有一個名為name
的字串引數。該函式應傳回一個字串,使用name
並說些類似於 “You’re wonderful, Bob.” 的話。 - 對於上面的函式,新增一個名為
numberPeople
的整數引數,以便函式傳回類似於 “You’re wonderful, Bob. 10 people think so.” 的字串。 - 對於上面的函式,將兩個輸入引數都設為命名引數。將
name
設為必需引數,並將numberPeople
的預設值設為 30。
解答
String youAreWonderful(String name) { return ‘You're wonderful, $name.’; }
2. ```dart
String youAreWonderful(String name, int numberPeople) {
return 'You\'re wonderful, $name. $numberPeople people think so.';
}
String youAreWonderful({ required String name, int numberPeople = 30, }) { return ‘You're wonderful, $name. $numberPeople people think so.’; }
## 撰寫良好的函式
良好的函式是軟體開發的基礎。一個良好的函式應該具有明確的目的、少量的程式碼和沒有副作用。以下是撰寫良好的函式的幾個原則。
### 避免副作用
副作用是指函式除了傳回值以外還會對外部環境產生影響。例如,列印輸出到主控臺或修改全域變數。副作用會使函式的行為更加複雜和難以預測。
```dart
void hello() {
print('Hello!');
}
上述函式具有副作用,因為它列印輸出到主控臺。為了避免副作用,可以改寫函式如下:
String hello() {
return 'Hello!';
}
現在,函式不再具有副作用,因為它只傳回一個字串。
只做一件事
一個良好的函式應該只做一件事。如果一個函式過於複雜或具有多個責任,應該將它分解為多個小函式。這樣可以使函式更容易理解和維護。
void calculateAreaAndPerimeter(int length, int width) {
int area = length * width;
int perimeter = 2 * (length + width);
print('Area: $area, Perimeter: $perimeter');
}
上述函式可以分解為兩個小函式:
int calculateArea(int length, int width) {
return length * width;
}
int calculatePerimeter(int length, int width) {
return 2 * (length + width);
}
void printAreaAndPerimeter(int length, int width) {
int area = calculateArea(length, width);
int perimeter = calculatePerimeter(length, width);
print('Area: $area, Perimeter: $perimeter');
}
選擇良好的名稱
函式名稱應該清晰地描述函式的目的。名稱應該簡短但具有描述性。
String getAverageTemperature(List<int> temperatures) {
// ...
}
可以改寫為:
String averageTemperature(List<int> temperatures) {
// ...
}
選擇性型別
Dart是一種選擇性型別的語言,這意味著可以省略型別宣告。然而,為了提高程式碼的可讀性和維護性,建議在可能的情況下新增型別宣告。
String compliment(int number) {
return '$number is a very nice number.';
}
可以省略型別宣告如下:
compliment(number) {
return '$number is a very nice number.';
}
但是,這樣會使得Dart無法推斷型別,從而可能導致錯誤。因此,建議在可能的情況下新增型別宣告。
圖表翻譯:
flowchart TD A[撰寫函式] --> B[避免副作用] B --> C[只做一件事] C --> D[選擇良好的名稱] D --> E[選擇性型別] E --> F[完成]
上述流程圖描述了撰寫良好的函式的步驟。首先,避免副作用;然後,只做一件事;接下來,選擇良好的名稱;最後,選擇性型別。透過遵循這些步驟,可以撰寫出高質量的函式。
Dart 中的函式
Dart 是一種支援物件導向程式設計的語言,函式是 Dart 中的一個基本單位。函式可以封裝一段程式碼,讓它可以被重複呼叫和使用。
箭頭函式
Dart 有一種特殊的語法,用於定義只有一個陳述式的函式。這種語法被稱為箭頭函式。箭頭函式的定義方式如下:
int add(int a, int b) => a + b;
箭頭函式的優點是可以讓程式碼更加簡潔和易於閱讀。
函式的定義
函式的定義包括函式的名稱、引數和傳回值。函式的名稱是用來呼叫函式的,引數是用來傳遞資料給函式的,傳回值是函式執行完畢後傳回的結果。
函式的呼叫
函式可以被呼叫多次,函式的呼叫方式如下:
int result = add(2, 3);
挑戰
現在,讓我們來做一些挑戰,來測試你的函式知識。
挑戰 1:圓的面積
寫一個函式,計算圓的面積,根據輸入的半徑。
挑戰 2:質數
寫一個函式,檢查一個數字是否是質數。
首先,寫一個函式,檢查一個數字是否可以被另一個數字整除:
bool isNumberDivisible(int number, int divisor) {
return number % divisor == 0;
}
然後,寫一個函式,傳回 true 如果數字是質數,否則傳回 false:
bool isPrime(int number) {
if (number < 2) {
return false;
}
for (int i = 2; i * i <= number; i++) {
if (isNumberDivisible(number, i)) {
return false;
}
}
return true;
}
測試以下案例:
print(isPrime(6)); // false
print(isPrime(13)); // true
print(isPrime(8893)); // true
類別的基礎
類別(Class)是物件導向程式設計(Object-Oriented Programming, OOP)中的核心概念。它可以被視為是一種藍圖或範本,描述瞭如何建立一個物件。物件是類別的例項,代表了具體的資料和行為。
類別的定義
在Dart中,類別是使用class
關鍵字定義的。類別的定義包括了類別的名稱、屬性(Properties)和方法(Methods)。屬性是類別的資料成員,方法是類別的行為成員。
class MyClass {
// 屬性
var myProperty;
// 方法
void myMethod() {
print(myProperty);
}
}
類別的例項化
要建立一個類別的例項,需要使用new
關鍵字。例如:
MyClass myObject = new MyClass();
類別的屬性和方法
類別的屬性可以是例項變數或靜態變數。例項變數是每個例項都有的獨立副本,靜態變數是所有例項共享的。
類別的方法可以是例項方法或靜態方法。例項方法是每個例項都有的獨立副本,靜態方法是所有例項共享的。
物件導向程式設計的基本概念
物件導向程式設計的基本概念包括:
- 封裝(Encapsulation):將資料和行為封裝在一個單元中。
- 繼承(Inheritance):一個類別可以繼承另一個類別的屬性和方法。
- 多型(Polymorphism):一個類別可以有多種不同的行為。
- 抽象(Abstraction):將複雜的系統簡化為一個簡單的模型。
類別的優點
類別的優點包括:
- 封裝:類別可以封裝資料和行為,減少程式的複雜性。
- 重用性:類別可以被重用,減少程式的開發時間。
- 繼承:類別可以繼承其他類別的屬性和方法,增加程式的靈活性。
圖表翻譯:
classDiagram class MyClass { - myProperty: var + myMethod(): void } note "MyClass 是一個類別,具有 myProperty 屬性和 myMethod 方法"
內容解密:
在上面的例子中,MyClass
是一個類別,具有myProperty
屬性和myMethod
方法。myProperty
是類別的資料成員,myMethod
是類別的行為成員。類別的例項化需要使用new
關鍵字。類別的屬性和方法可以是例項變數或靜態變數,例項方法或靜態方法。物件導向程式設計的基本概念包括封裝、繼承、多型和抽象。類別的優點包括封裝、重用性、繼承和靈活性。
類別的基礎
類別是物件導向程式設計中的基本概念。它是一種範本,定義了物件的屬性和行為。在Dart中,類別是使用class
關鍵字定義的。
定義類別
以下是定義一個簡單的User
類別的範例:
class User {
int id = 0;
String name = '';
}
這個類別有兩個屬性:id
和name
。id
是整數型別,預設值為0;name
是字串型別,預設值為空字串。
建立物件
要建立一個物件,需要使用類別的名稱後面加上括號,例如:
final user = User();
這會建立一個新的User
物件,並將其指定給user
變數。
屬性存取
可以使用點符號(.)來存取物件的屬性,例如:
user.name = 'Ray';
user.id = 42;
這會將name
屬性設定為"Ray",將id
屬性設定為42。
建構函式
在Dart中,建構函式是一種特殊的函式,用於建立物件。它的名稱與類別名稱相同,且沒有傳回型別。以下是User
類別的建構函式範例:
class User {
int id;
String name;
User({this.id = 0, this.name = ''});
}
這個建構函式有兩個引數:id
和name
,它們都有預設值。
物件的建立
可以使用建構函式來建立物件,例如:
final user = User(id: 42, name: 'Ray');
這會建立一個新的User
物件,並將其指定給user
變數。
內容解密:
上述程式碼定義了一個簡單的User
類別,具有id
和name
屬性。然後,建立了一個新的User
物件,並將其指定給user
變數。最後,使用點符號存取物件的屬性,並將其設定為新的值。
flowchart TD A[定義類別] --> B[建立物件] B --> C[存取屬性] C --> D[設定屬性值]
圖表翻譯:
此圖表示了建立物件和存取其屬性的過程。首先,定義了一個類別,然後建立了一個新的物件。接著,使用點符號存取物件的屬性,並將其設定為新的值。
類別的基礎
在Dart中,類別是用來定義物件的藍圖。您可以使用類別來建立新的物件,並為其定義屬性和方法。讓我們來看一個簡單的例子:
class User {
int id = 0;
String name = '';
}
在這個例子中,我們定義了一個名為User
的類別,它有兩個屬性:id
和name
。您可以使用這個類別來建立新的User
物件,並為其定義屬性值。
列印物件
當您列印一個物件時,Dart會呼叫該物件的toString()
方法。預設情況下,toString()
方法會傳回一個字串,指示物件的類別名稱和例項。讓我們來看一個例子:
User user = User();
user.id = 42;
user.name = 'Ray';
print(user);
如果您執行這段程式碼,輸出將會是Instance of 'User'
。這不是很有用,因為我們想要看到id
和name
的值。為瞭解決這個問題,我們可以覆寫toString()
方法:
class User {
int id = 0;
String name = '';
@override
String toString() {
return 'User(id: $id, name: $name)';
}
}
現在,如果您執行相同的程式碼,輸出將會是User(id: 42, name: Ray)
。
序列化物件
序列化是指將物件轉換為字串或其他可儲存或傳輸的格式。Dart提供了多種序列化格式,包括JSON。讓我們來看一個例子:
class User {
int id = 0;
String name = '';
String toJson() {
return '{"id":$id,"name":"$name"}';
}
}
在這個例子中,我們定義了一個名為toJson()
的方法,它將User
物件轉換為JSON字串。您可以使用這個方法來序列化User
物件:
User user = User();
user.id = 42;
user.name = 'Ray';
print(user.toJson());
輸出將會是{"id":42,"name":"Ray"}
。
物件導向程式設計中的物件參考
在 Dart 中,物件被視為記憶體中類別例項的參考。這意味著當您將一個物件指派給另一個物件時,後者只會持有對相同記憶體位置的參考,而不是建立一個新的例項。
物件參考的示例
class MyClass {
var myProperty = 1;
}
void main() {
final myObject = MyClass();
final anotherObject = myObject;
print(myObject.myProperty); // 1
anotherObject.myProperty = 2;
print(myObject.myProperty); // 2
}
在這個例子中,myObject
和 anotherObject
都參考相同的記憶體位置。因此,當我們修改 anotherObject
的 myProperty
時,也會影響 myObject
的 myProperty
。
物件導向程式設計中的封裝
封裝是物件導向程式設計中的核心原則之一,指的是隱藏類別中的內部資料和邏輯,僅暴露必要的介面給外部世界。這樣做有幾個好處:
- 類別控制自己的資料,包括誰可以存取和修改它。
- 隔離控制使得邏輯更容易理解。
- 隱藏資料意味著更好的隱私性。
- 封裝防止無關的類別存取和修改資料,從而減少難以找到錯誤的可能性。
- 你可以在不破壞外部世界的前提下修改內部變數名稱和邏輯。
實作封裝
在Dart中,你可以透過使變數私有化來實作封裝。要使變數私有化,只需在變數名稱前新增底線(_)即可。例如:
class Password {
String _plainText = 'pass123';
}
這樣,_plainText
變數就變成了私有變數,外部無法直接存取它。
Getter
Getter是一種特殊的方法,傳回私有欄位變數的值。它是私有變數的公共介面。如果你曾經使用過Java或C++,你可能見過以getColor
或getWidth
命名的getter方法。在Dart中,你可以使用get
關鍵字定義getter。
class Password {
String _plainText = 'pass123';
String get plainText => _plainText;
}
現在,你可以使用plainText
屬性存取_plainText
變數的值。
final myPassword = Password();
final text = myPassword.plainText;
這樣,你就可以控制外部如何存取私有變數的值,並且保持了封裝原則。
隱藏內部實作
透過使用getter,你可以隱藏類別的內部實作,僅暴露必要的介面給外部世界。這使得你的程式碼更容易維護和擴充套件。
class Password {
String _plainText = 'pass123';
String get plainText => _plainText;
}
在這個例子中,_plainText
變數是私有的,外部無法直接存取它。但是,透過plainText
getter,外部可以存取它的值。
內容解密:
上述程式碼定義了一個Password
類別,包含一個私有變數_plainText
和一個getter方法plainText
。getter方法傳回私有變數的值,使得外部可以存取它的值,而不需要直接存取私有變數。
圖表翻譯:
classDiagram class Password { - _plainText: String + plainText(): String }
此圖表顯示了Password
類別的結構,包括私有變數_plainText
和getter方法plainText()
。
類別中的Getter和Setter
在Dart中,getter和setter是類別中用於控制對物件屬性存取的重要工具。getter允許你定義一個方法,該方法不需要括號就可以被呼叫,傳回一個值。setter則允許你定義一個方法,該方法可以用於設定一個值。
Getter
Getter是一種特殊的方法,允許你從物件中取得一個值,而不需要顯式地呼叫一個方法。以下是定義一個getter的例子:
class Password {
String _plainText;
String get plainText => _plainText;
}
在這個例子中,plainText
是一個getter,傳回 _plainText
的值。注意,getter不需要括號就可以被呼叫。
Setter
Setter是一種特殊的方法,允許你設定一個值到物件中。以下是定義一個setter的例子:
class Password {
String _plainText;
set plainText(String text) => _plainText = text;
}
在這個例子中,plainText
是一個setter,設定 _plainText
的值。
計算屬性
計算屬性是一種特殊的getter,傳回一個計算出的值。以下是定義一個計算屬性的例子:
class Password {
String _plainText;
String get obfuscated {
final length = _plainText.length;
return '*' * length;
}
}
在這個例子中,obfuscated
是一個計算屬性,傳回一個由星號組成的字串,長度與 _plainText
相同。
使用Getter和Setter
以下是使用getter和setter的例子:
final myPassword = Password();
final text = myPassword.obfuscated;
print(text); // 輸出:*******
myPassword.plainText = '123456';
print(myPassword.plainText); // 輸出:123456
在這個例子中,myPassword
是一個 Password
物件,obfuscated
是一個計算屬性,傳回一個由星號組成的字串。plainText
是一個getter和setter,允許你取得和設定 _plainText
的值。
使用Getter和Setter進行資料驗證
在Dart中,Getter和Setter可以用來控制對物件屬性的存取。以下是如何使用Setter進行資料驗證的範例:
class Password {
String _plainText = '';
String get plainText => _plainText;
set plainText(String text) {
if (text.length < 6) {
print('密碼必須有6個或以上的字元!');
return;
}
_plainText = text;
}
}
在這個範例中,Setter會檢查密碼的長度是否少於6個字元,如果是,則會印出警告訊息並不更新密碼。
測試Setter
以下是測試Setter的範例:
void main() {
final shortPassword = Password();
shortPassword.plainText = 'aaa';
final result = shortPassword.plainText;
print(result);
}
執行這個程式,會印出以下的輸出:
密碼必須有6個或以上的字元!
密碼並沒有被更新為aaa
。
不需要過度使用Getter和Setter
如果只需要簡單地存取一個內部變數,則不需要使用Getter和Setter。例如,以下的類別:
class Email {
String _value = '';
String get value => _value;
set value(String value) => _value = value;
}
可以簡化為:
class Email {
String value = '';
}
這樣可以減少不必要的程式碼。
類別與建構式
在物件導向程式設計中,類別是封裝資料和方法的基本單位。Dart 中的類別可以定義屬性和方法,屬性是類別中的變數,方法是類別中的函式。
從技術架構視角來看,Dart 的函式和類別設計體現了現代程式語言的特性,兼顧了簡潔性、靈活性和安全性。命名引數、預設值、箭頭函式等語法糖簡化了程式碼,提升了可讀性。類別的封裝、繼承、多型等特性則提供了建構複雜應用程式的基礎。然而,Dart 的選擇性型別系統雖然提供了靈活性,但也可能隱藏型別錯誤的風險,需要開發者更加謹慎地處理型別推斷和型別檢查。展望未來,隨著 Dart 生態系統的持續發展,預計會有更多工具和最佳實踐出現,以幫助開發者更好地利用 Dart 的特性,構建更健壯、高效的應用程式。對於追求程式碼品質和長期維護性的團隊,深入理解 Dart 的型別系統和物件導向程式設計原則至關重要。