前言
Dart 語言的構(gòu)造函數(shù)和其他語言會(huì)有些不同,我們列舉一下 Dart 中的構(gòu)造函數(shù)的幾種形式。
// 最常見的形式
class Person {
String name;
int age;
Person(this.name, this.age);
}
// 命名構(gòu)造函數(shù)
class Person {
late String name;
late int age;
Person.fromJson(Map<String, dynamic> json) {
name = json['name'];
age = json['age'];
}
}
// 工廠構(gòu)造函數(shù)
class Person {
late String name;
static final Map<String, Person> _cache =
<String, Person>{};
factory Person.withName(String name) {
return _cache.putIfAbsent(
name, () => Person._internal(name));
}
Person._internal(this.name);
}
// 常量構(gòu)造函數(shù)
class Person {
final String name;
final int age;
const Person(this.name, this.age);
}
// 使用其他構(gòu)造函數(shù)構(gòu)造
class Person {
String name;
int age;
Person(this.name, this.age);
Person.anymous(int age): this('Anymous', age);
}
// 帶參數(shù)斷言的構(gòu)造函數(shù)(debug 模式有效)
class Person {
String name;
int age;
Person.withAssert(this.name, this.age)
: assert(age >= 0),
assert(name.length > 0);
}
// 使用父類構(gòu)造函數(shù)構(gòu)建
class Student extends Person {
String school;
Student(name, age, this.school) : super(name, age);
}
這里比較容易混淆的是命名構(gòu)造函數(shù)和工廠構(gòu)造函數(shù)。實(shí)際上工廠構(gòu)造函數(shù)的特點(diǎn)是不一定返回新的實(shí)例,比如我們示例的代碼,可以從緩存中取出已有對(duì)象返回。同時(shí),工廠構(gòu)造函數(shù)還可以返回該類的子類。而且工廠構(gòu)造函數(shù)是要通過 return 語句返回一個(gè)對(duì)象,而命名構(gòu)造函數(shù)不允許返回,只能構(gòu)建當(dāng)前類的對(duì)象。這么多構(gòu)造函數(shù),該如何合理使用,我們來看看官方的指引規(guī)范。
規(guī)則1:盡可能使用構(gòu)造函數(shù)直接初始化成員的方式
Dart 提供了一種快捷初始化成員屬性的方式,那就是在構(gòu)造函數(shù)中使用 this.語法,這時(shí)候?qū)?yīng)的成員會(huì)自動(dòng)賦值而無需手動(dòng)進(jìn)行賦值。這樣的初始化成員的方式更加簡(jiǎn)潔。
// 正確示例
class Point {
double x, y;
Point(this.x, this.y);
}
// 錯(cuò)誤示例
class Point {
double x, y;
Point(double x, double y)
: x = x,
y = y;
}
規(guī)則2:如果構(gòu)造函數(shù)會(huì)初始化成員,那么不要使用 late 修飾。
聲明式 null safety 要求非空字段必須在使用之前被初始化。由于成員屬性可能在構(gòu)造函數(shù)中使用,因此如果不初始化非空成員的話,會(huì)導(dǎo)致編譯器報(bào)錯(cuò)??梢酝ㄟ^在成員前加 late 修飾,這時(shí)候如果不初始化而直接使用該成員的話 ,會(huì)將編譯時(shí)錯(cuò)誤轉(zhuǎn)換為運(yùn)行時(shí)錯(cuò)誤。這種情況下最好的解決辦法是為構(gòu)造函數(shù)提供成員清單進(jìn)行初始化。
// 正確示例
class Point {
double x, y;
Point.polar(double theta, double radius)
: x = cos(theta) * radius,
y = sin(theta) * radius;
}
// 錯(cuò)誤示例
class Point {
late double x, y;
Point.polar(double theta, double radius) {
x = cos(theta) * radius;
y = sin(theta) * radius;
}
}
使用初始化清單的形式的好處是在使用這些成員之前能夠確保已經(jīng)得到了初始化,哪怕是構(gòu)造函數(shù)中。我們可以理解為是一種語法糖,在構(gòu)造函數(shù)最開始處對(duì)這些成員進(jìn)行了初始化。
規(guī)則3:對(duì)于空構(gòu)造函數(shù)體,使用分號(hào)結(jié)束而不是{}空函數(shù)體
在 Dart 中,如果構(gòu)造函數(shù)體為空,那么可以簡(jiǎn)單地用分號(hào)結(jié)束函數(shù),而無需使用大括號(hào)來寫一個(gè)空的函數(shù)體。
// 正確示例
class Point {
double x, y;
Point(this.x, this.y);
}
//錯(cuò)誤示例
class Point {
double x, y;
Point(this.x, this.y) {}
}
規(guī)則4:不要使用 new 來構(gòu)建新對(duì)象
Dart 2版本以后,new 關(guān)鍵字是可選的。new 關(guān)鍵字實(shí)際上在 Dart 中已經(jīng)被標(biāo)記為棄用了。當(dāng)你習(xí)慣了沒有 new 的方式后,你肯定會(huì)和我一樣,覺得加了 new 關(guān)鍵字的代碼很丑陋,尤其是在組件樹中。
// 正確示例
Widget build(BuildContext context) {
return Row(
children: [
RaisedButton(
child: Text('Increment'),
),
Text('Click!'),
],
);
}
// 錯(cuò)誤示例
Widget build(BuildContext context) {
return new Row(
children: [
new RaisedButton(
child: new Text('Increment'),
),
new Text('Click!'),
],
);
}
規(guī)則5:不要加多余的 const
當(dāng)一個(gè)對(duì)象在上下文中必須是常量時(shí),const 關(guān)鍵字是隱式的,而無需再單獨(dú)寫。下面列舉的上下文中的表達(dá)式是默認(rèn)就是 const 的:
- 一個(gè)不變的集合;
- 調(diào)用聲明為
const的構(gòu)造函數(shù); - 元數(shù)據(jù)的注解;
- 用于常量聲明時(shí)的初始化對(duì)象(見下面的示例)
-
switch-case語句中的case冒號(hào)后面的表達(dá)式。
// 正確示例
const primaryColors = [
Color('red', [255, 0, 0]),
Color('green', [0, 255, 0]),
Color('blue', [0, 0, 255]),
];
// 錯(cuò)誤示例
const primaryColors = const [
const Color('red', const [255, 0, 0]),
const Color('green', const [0, 255, 0]),
const Color('blue', const [0, 0, 255]),
];
基本上,在 Dart 2中,如果一個(gè)對(duì)象前面加 new 來替代 const 會(huì)報(bào)錯(cuò)的話,那么就隱式是 const 的,此時(shí)無需再額外加 const。
總結(jié)
Dart 語言為我們提供了很多簡(jiǎn)寫構(gòu)造函數(shù)的語法糖,雖然按其他語言那種形式編寫也不會(huì)有什么問題,但是遵循舊的方式如何能夠體現(xiàn) Dart 這門年輕語言的優(yōu)勢(shì)呢?因此,多了解 Dart 語言自身的一些特性,可以讓我們的編碼效率更高,代碼更整潔。