前言
我們寫 Dart 代碼的時候苦银,變量和類成員天天用莹妒,但是用的方式一定對嗎?恐怕未必舞吭,本篇我們來介紹變量和類成員該如何合理使用县耽。
規(guī)則1:局部變量使用 var和 final 的方式要保持一致
對于大部分局部變量,無需指定類型镣典,而應該是使用 var
或 final
兔毙,應該從下面的兩條規(guī)則中選擇一條:
- 對于不會重復賦值的使用
final
,其他的使用var
兄春。這個其實看似很容易遵循澎剥,但是編寫的時候很容易忽略。一個經(jīng)驗就是赶舆,優(yōu)先使用final
哑姚,如果發(fā)現(xiàn)后面需要重新賦值的時候再使用var
。 - 對于局部變量芜茵,只使用
var
叙量,即便是那些不會重新賦值的局部變量,也就是對于局部變量不使用final
九串。注意绞佩,這里的局部變了指的是函數(shù)內(nèi)部的局部變量寺鸥,類的成員變量當然可以使用final
修飾。這一條更容易遵循一些品山。
一旦你選擇了上面中的一條胆建,那么應該一直遵循下去,要不有強迫癥的碼農(nóng)看到你的代碼后會肯定會冒出一堆問題 —— “大佬肘交,這里為什么用 final笆载?這里為什么又不用 final?”估計最后尷尬的你只能“呃……”涯呻,然后找個理由搪塞過去了凉驻。
規(guī)則2:不要存儲那些計算變量
計算變量是指可以通過別的類成員計算出來的屬性。當你存儲的時候复罐,會導致很多問題沿侈,比如你可能需要在各個關聯(lián)屬性變更的地方埋點更新這個計算變量,一旦遺漏就會出現(xiàn) bug市栗。例如下面的例子就是一個典型的反面例子缀拭。
// 錯誤示例
class Circle {
double radius;
double area;
double circumference;
Circle(double radius)
: radius = radius,
area = pi * radius * radius,
circumference = pi * 2.0 * radius;
}
面積和周長都是依賴于半徑的。上面這種方式有兩個缺陷:
- 增加了兩個成員屬性來緩存面積和周長填帽,浪費了存儲空間蛛淋;
- 存在不同步的隱患,一旦半徑更改了篡腌,如果不主動更新面積和周長褐荷,就會出現(xiàn)不一致的情況。要解決這個問題嘹悼,需要在半徑改變的時候更新面積和周長:
// 錯誤示例
class Circle {
double _radius;
double get radius => _radius;
set radius(double value) {
_radius = value;
_recalculate();
}
double _area = 0.0;
double get area => _area;
double _circumference = 0.0;
double get circumference => _circumference;
Circle(this._radius) {
_recalculate();
}
void _recalculate() {
_area = pi * _radius * _radius;
_circumference = pi * 2.0 * _radius;
}
}
代碼又臭又長叛甫,對吧?正確的做法是用 get 來獲取計算屬性就可以了杨伙,像下面的代碼是不是超級清爽其监?
// 正確示例
class Circle {
double radius;
Circle(this.radius);
double get area => pi * radius * radius;
double get circumference => pi * 2.0 * radius;
}
當然,這個規(guī)則也不是一成不變的限匣,假設你的計算過程非常復雜抖苦,而且變量改變?nèi)肟诓欢啵敲绰暶饕粋€成員屬性來緩存計算結(jié)果會節(jié)省大量 CPU 的開銷米死,也就是已空間換時間锌历。這個時候是可以這么做的,具體怎么選擇需要自己根據(jù)代價去判斷峦筒。
規(guī)則3:如無必要究西,不要為類成員提供 getter 和 setter
在 Java 或 C#這類語言中,通常推薦是將所有成員屬性隱藏物喷,然后對外提供 getter 和 setter 來訪問卤材,這是因為這兩門語言對 getter遮斥,setter 和直接訪問屬性的處理方式不同。而在 Dart 里面商膊,通過成員訪問和使用 getter 和 setter 是沒有區(qū)別的伏伐。因此宠进,如果一個成員對外完全可以訪問(包括 getter 和 setter)晕拆,那么就沒必要使用 getter 和 setter。當然材蹬,如果一個成員對外只能進行 setter 或 getter实幕,那么就需要單獨提供對應的方法,而屏蔽另一個堤器。
// 正確示例
class Box {
Object? contents;
}
// 錯誤示例
class Box {
Object? _contents;
Object? get contents => _contents;
set contents(Object? value) {
_contents = value;
}
}
規(guī)則4:對只讀成員使用 final 修飾
如果你的成員屬性對外部是只讀的昆庇,那么應該使用 final
修飾,而不是提供 getter
訪問闸溃。當然整吆,這個前提是這個成員屬性初始化之后不會再重新賦值。
// 正確示例
class Box {
final contents = [];
}
// 錯誤示例
class Box {
Object? _contents;
Object? get contents => _contents;
}
規(guī)則5:對于簡單的計算返回值辉川,優(yōu)先使用 => 表達式
這個其實是為了簡化代碼表蝙,提高可讀性的一個指引。對于使用簡單的表達式返回一個計算屬性或調(diào)用方法時乓旗,使用=> 表達式更加簡潔易懂府蛇。
// 正確示例
double get area => (right - left) * (bottom - top);
String capitalize(String name) =>
'${name[0].toUpperCase()}${name.substring(1)}';
當然,假設計算返回值的代碼有好幾行屿愚,那么還是使用正常的函數(shù)形式更好汇跨。
// 正確示例
Treasure? openChest(Chest chest, Point where) {
if (_opened.containsKey(chest)) return null;
var treasure = Treasure(where);
treasure.addAll(chest.contents);
_opened[chest] = treasure;
return treasure;
}
// 錯誤示例
Treasure? openChest(Chest chest, Point where) => _opened.containsKey(chest)
? null
: _opened[chest] = (Treasure(where)..addAll(chest.contents));
實際上,原則就是根據(jù)代碼的可讀性決定選擇哪種方式妆距,而不是生搬硬套穷遂。對于成員屬性來說也可以使用 => 形式,例如下面的情況會讓代碼更簡潔娱据。
num get x => center.x;
set x(num value) => center = Point(value, center.y);
除非要和同名參數(shù)做區(qū)分塞颁,否則不要使用 this.來訪問成員
使用 this.
訪問類成員在很多語言中很常見,但在 Dart 中不推薦吸耿。 對于訪問成員只有兩種情況需要使用 this.
:
- 構(gòu)造方法中使用 this. 表名構(gòu)造函數(shù)的參數(shù)是用于設置成員變量的祠锣;
- 其他函數(shù)中的參數(shù)名和類成員屬性同名,需要使用
this.
來區(qū)分咽安。
// 正確示例
class Box {
Object? value;
void clear() {
update(null);
}
String toString() {
return 'Box: $value';
}
void update(Object? value) {
this.value = value;
}
}
// 錯誤示例
class Box {
Object? value;
void clear() {
this.update(null);
}
String toString() {
return 'Box: ${this.value}';
}
void update(Object? value) {
this.value = value;
}
}
另外用到 this.的場合包括使用構(gòu)造方法來完成構(gòu)造命名構(gòu)造器:
class ShadeOfGray {
final int brightness;
ShadeOfGray(int val) : brightness = val;
// 使用構(gòu)造方法
ShadeOfGray.black() : this(0);
// 使用命名構(gòu)造方法
ShadeOfGray.alsoBlack() : this.black();
}
盡可能在成員聲明的時候初始化
如果成員屬性不依賴于構(gòu)造參數(shù)伴网,那么可以在聲明的時候進行初始化,這會使得代碼量更少妆棒,而且避免了因為類有多個構(gòu)造器導致重復代碼出現(xiàn)的情況澡腾。
// 正確示例
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
// 錯誤示例
class ProfileMark {
final String name;
final DateTime start;
ProfileMark(this.name) : start = DateTime.now();
ProfileMark.unnamed()
: name = '',
start = DateTime.now();
}
有些成員沒法在一開始初始化沸伏,是因為這些成員依賴于其他成員或需要調(diào)用方法來初始化。這個時候动分,應該用 late
來修飾毅糟,這個時候可以訪問 this
來進行初始化。
class _AnimatedModelBarrierDemoState extends State<AnimatedModelBarrierDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this);
late Animation<Color?> _colorAnimation = ColorTween(
begin: Colors.black.withAlpha(50),
end: Colors.black.withAlpha(80),
).animate(_controller);
// ...
}
總結(jié)
可以看到澜公,實際上代碼的寫法有很多種姆另,所謂“寫法千萬種,規(guī)范第一條坟乾;代碼不規(guī)范迹辐,同事兩行淚”∩趼拢“碼農(nóng)何苦為難碼農(nóng)”呢?有了規(guī)范指引明吩,才能夠?qū)懗龈哔|(zhì)量代碼。