Dart基礎: 構造函數(shù)
Dart的構造函數(shù)比較變化多端, 我們可以在不同的場景下合理聲明和調(diào)用一個class的構造函數(shù). 在實際開發(fā)過程中, 其實會遇到各種不同的情況, 需要我們合理使用構造函數(shù). 因此我們需要對構造函數(shù)有充分的了解才可以做出合理的選擇.
聲明 & 調(diào)用
首先假設我們有一個class如下
class Person {
int id;
int age;
String name;
}
--
直接指向變量名的聲明方式
// 參數(shù)分別指向`Person`對應的變量, 都是必填項.
Person(this.id, this.age, this.name);
調(diào)用:
// 調(diào)用的時候, 函數(shù)參數(shù)沒有形式參數(shù)名
var person = Person(0, 18, 'name_of_person');
這種情況下:
- 可以確保參數(shù)都有傳入.
- 參數(shù)傳入順序和聲明時候的順序要保持一致.
- 在缺少參數(shù)的情況下會導致編譯失敗.
- 沒有明確參數(shù)名稱, 當參數(shù)較多的時候代碼可讀性不高.
--
可選參數(shù)聲明方式1
/*
用花括號把可選參數(shù)包裹
同樣是指向`Person`對應的變量, 都是可選參數(shù).
*/
Person({this.id, this.age, this.name});
調(diào)用:
Person(id: 0, name: 'name_of_person', age: 18);
// 或
Person(id: 0);
// 或
Person(name: 'name_of_person');
這種情況下:
- 有明確的參數(shù)名稱, 在參數(shù)較多的時候仍然可以保持較高可讀性.
- 傳入?yún)?shù)的順序可以不固定, 只需要對應其參數(shù)名即可.
- 可以跳過部分參數(shù)不傳入.
- 有導致對象初始化后部分變量為
null.
--
可選參數(shù)聲明方式2
// 用方括號把可選參數(shù)包裹
Person([this.id, this.age, this.name]);
調(diào)用:
Person();
// 或
Person(0, 18);
// 或
Person(0, 18, 'name_of_person');
這種情況下:
- 調(diào)用的時候不會顯示參數(shù)名.
- 傳參順序必須按照聲明順序.
- 傳參的時候不能跳過可選參數(shù)中間的某個參數(shù), 比如上述例子, 不可以傳入
id和name而跳過age.
--
使用@required關鍵字的可選參數(shù)聲明方式:
// `id`參數(shù)被聲明`@required`, 在調(diào)用的時候必須傳入.
Person({@required this.id, this.name, this.age});
調(diào)用:
Person(id: 0, name: 'name_of_person')
// 如果不傳`id`會引起編譯警告
Person(age: 18, name: 'name_of_person')
這種情況下:
- 通過編譯器警告提示使用者需要傳入必須的參數(shù).
- 只能修飾花括號聲明方式下的可選參數(shù).
- 僅有提示作用, 即使不傳入?yún)?shù)也可以編譯通過.
--
帶默認參數(shù)的聲明方式
// `name`的值默認為'unnamed'
Person({this.id, this.age, this.name = 'unnamed'});
這種情況下:
- 一個變量在沒有傳參的情況下, 初始化的值就是其默認值.
- 默認值的聲明方式只可以用在可選型參數(shù)上.
- 默認值必須是靜態(tài)的.
--
可選參數(shù)聲明的方式看起來很美好, 但是它有一個局限性:
它可以綁定到class的公共變量, 但是無法綁定私有變量.
那我們又想在構造函數(shù)里完成對私有變量的賦值, 又想把這個參數(shù)設置成可選的, 是不是就沒有辦法了?
這個問題是可以解決的, 下面將會提到.
--
以上幾種聲明方式可以混合使用:
Person(this.id, {@required this.age, this.name = 'unnamed'});
調(diào)用:
Person(0, age: 18);
Person(0, name: 'name_of_person', age: 18);
這種情況下:
- 在聲明時必傳參數(shù)必須都聲明在前.
- 調(diào)用的時候, 傳參順序: 先按照必傳參數(shù)的聲明順序傳入, 然后才可以傳可選型參數(shù).
- 可選型參數(shù)的順序可以任意.
--
我們可以發(fā)現(xiàn), 以上幾種聲明方式都是在構造函數(shù)中直接把值綁定到class的變量.
而這樣調(diào)用構造函數(shù), 我們必須在之前就把全部參數(shù)都處理完畢. 這樣往往會導致我們每次創(chuàng)建一個對象前都先做一堆處理, 而我們很多時候并不希望這樣.
我們更希望構造函數(shù)自身可以完成這些處理. dart其實也提供了這種聲明方式:
Person(Map map)
: this.id = map['id'],
this.name = map['name'],
this.age = map['age'];
這樣我們就可以通過傳入一個Map對象的方式實現(xiàn)Person的初始化. 客戶端和服務端數(shù)據(jù)請求之后對http響應數(shù)據(jù)做轉換的時候, 我們面對的就是這種場景.
而上面提到過的私有變量作為可選參數(shù)的問題, 就可以通過這個聲明方式解決. 假設我們增加一個私有變量_deposit, 我們可以用這個形式做聲明:
class Person {
int id;
int age;
String name;
int _deposit;
/*
其余幾個參數(shù)會自動綁定, _deposit通過人為手動賦值, 實現(xiàn)required可選參數(shù)
*/
Person({
this.id,
this.age,
this.name,
@required int deposit,
}) : this._deposit = deposit;
}
--
而實際上, 有時候我們需要做的處理會更加復雜, 用上面這個方式做變量賦值就滿足不了我們了. 這個時候我們還有一種聲明方式:
Person(Map map) {
this.id = int.parse(map['id'].toString()) ?? 0;
this.age = int.parse(map['age'].toString()) ?? 0;
var nameInMap = map['name'];
this.name = nameInMap is String ? nameInMap : 'unnamed';
}
這種聲明方式可以在構造函數(shù)中做較復雜的數(shù)據(jù)處理, 但是有一種情況下并不好使:
用final修飾的變量
如果我們的Person類型做出以下調(diào)整:
class Person {
final int id;
int age;
String name;
}
那么這個id變量就無法在構造函數(shù)的函數(shù)體內(nèi)做賦值操作, 我們只能通過之前的兩種聲明方式來對它賦值.
--
然后這個時候我們可能會碰到一個問題:
如果我們需要通過Map對象的構造函數(shù), 同時也需要直接傳參的構造函數(shù), 我們發(fā)現(xiàn)我們同時聲明這兩種函數(shù), 會得到一個編譯器的警告
The default constructor is already defined.
Try giving one of the constructors a name.
嗯, 對的. 我們上面說了那么多種聲明方式, 在dart語言里面, 都等于是聲明了一個默認構造函數(shù). 而實際上, 其實我們不另外聲明構造函數(shù)的話, dart確實就會給Person提供一個構造函數(shù).
調(diào)用dart默認構造函數(shù):
Person();
當我們需要同時擁有多個構造函數(shù)的時候, 我們需要使用別的方式.
就拿這個Map的創(chuàng)建方式舉例:
// 用`.` + 構造函數(shù)名 的方式聲明構造函數(shù)
Person.fromMap(Map map)
: this.id = map['id'],
this.name = map['name'],
this.age = map['age'];
// 或者
Person.fromMap(Map map) {
this.id = int.parse(map['id'].toString()) ?? 0;
this.age = int.parse(map['age'].toString()) ?? 0;
var nameInMap = map['name'];
this.name = nameInMap is String ? nameInMap : 'unnamed';
}
調(diào)用方式:
// 用`.`的方式調(diào)起構造函數(shù)
Person.fromMap(someMap);
這種情況下:
- 參數(shù)聲明的規(guī)則和默認構造函數(shù)的規(guī)則是一致的.
- 可以根據(jù)不同情況選用不同的構造函數(shù).
- 對于
final修飾的變量, 同樣無法在構造函數(shù)的函數(shù)體內(nèi)進行賦值.
選用原則
從上我們已經(jīng)可以看出, dart的構造函數(shù)聲明方式有非常多種. 我的使用選擇主要依從以下幾個原則:
- 調(diào)用的時候一眼可以看出需要傳遞什么參數(shù).
- 參數(shù)在2個以上的時候選擇
@required的顯式參數(shù). - 避免調(diào)用前需要做過多的數(shù)據(jù)處理, 直接傳入其他類型, 在構造函數(shù)中做處理.
- 通過其他類型初始化的要使用有名字的構造函數(shù).
- 通過直接變量賦值的使用默認構造函數(shù).
更加簡單地概括, 就是兩個:
- 利用編譯器和命名做盡量多的提示.
- 在構造函數(shù)中包攬盡量多的工作.