Dart官方文檔:https://dart.dev/language/extension-methods
重要説明:本博客基於Dart官網文檔,但並不是簡單的對官網進行翻譯,在覆蓋核心功能情況下,我會根據個人研發經驗,加入自己的一些擴展問題和場景驗證。
擴展方法概述
當我們使用了一些被廣泛使用的其他庫或者自己的庫時,我們不太可能去修改這個庫API,但是我們又想給庫增加一些方法,該怎麼辦?如:我們想給String類增加一些我自己常用的方法。
Dart作為一門集百家之長的編程語言,也考慮到了這個需求點,它提供了一個擴展方法(Extension methods)來解決問題問題。
如下代碼樣例,String類型轉換int數字類型,常規的做法如下:
int.parse('123');
那如果String類型提供一個轉為int數字類型的方法,是不是更好:
'123'.parseInt()
想要實現上訴目的,通過擴展String類型,提供對應方法即可:
import './19-ntopic-string-apis.dart';
void main() {
print('123'.parseInt());
// 結果:123
}
//
// 19-ntopic-string-apis.dart 內容
//
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
使用擴展方法
上一章節的最後,其實我們已經展示瞭如何定義和使用擴展方法。使用擴展方法,和使用類型的其他方法沒有任何差異。
接下來我們來看看,擴展方法在靜態類型和動態類型的使用,和如何解決同名擴展方法衝突。
靜態類型和dynamic動態類型
特別注意:dynamic動態類型禁止使用擴展方法!如下代碼樣例,會拋出NoSuchMethodError運行時異常。
import './19-ntopic-string-apis.dart';
void main() {
print('123'.parseInt());
// 結果:123
dynamic d = '2';
// NoSuchMethodError: Class 'String' has no instance method 'parseInt'.
print(d.parseInt());
}
但是擴展方法可用於類型推導上,如下代碼無任何問題,因為變量v的類型推導成String類型:
import './19-ntopic-string-apis.dart';
void main() {
print('123'.parseInt());
// 結果:123
var v = '2';
print(v.parseInt());
// 結果:2
dynamic d = '2';
// NoSuchMethodError: Class 'String' has no instance method 'parseInt'.
print(d.parseInt());
}
dynamic動態類型不可用户擴展方法的原因是,擴展方法只能接收靜態類型,因此調用擴展方法和調用靜態方法一樣高效。
擴展方法衝突
我們在應用中,會引入多個庫,如果有多個庫都對同類型增加了同名的拓展方法,那麼就導出擴展方法衝突了。如:對String類型,庫A和庫B都有pareInt()擴展方法,那麼這個擴展方法就存在衝突。
一般情況下,有3種方法來解決擴展方法的衝突:
第一種方法,在引入庫時,通過show或者hide關鍵字限制擴展方法:
// String擴展方法:parseInt()
import 'string_apis.dart';
// String擴展方法:parseInt(), `hide`隱藏擴展類型
import 'string_apis_2.dart' hide NumberParsing2;
// ···
// 使用了 'string_apis.dart' 中定義的擴展方法:parseInt()
print('42'.parseInt());
第二種方法,顯示指定擴展類型的擴展方法:
// 擴展類型:NumberParsing,擴展方法:parseInt()
import 'string_apis.dart';
// 擴展類型:NumberParsing2,擴展方法:parseInt()
import 'string_apis_2.dart';
// 顯示使用擴展類型
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
第三種方法,假設第二種方法的擴展類型也一樣,那麼可在引入庫增加前綴解決:
// 擴展類型:NumberParsing,擴展方法:parseInt()
import 'string_apis.dart';
// 擴展類型:NumberParsing,擴展方法:parseInt(),parseNum()
import 'string_apis_3.dart' as rad;
// 'string_apis.dart' 擴展方法:parseInt()
print(NumberParsing('42').parseInt());
// 'string_apis_3.dart' 擴展方法:parseInt()
print(rad.NumberParsing('42').parseInt());
// 'string_apis_3.dart' 擴展方法:parseNum()
print('42'.parseNum());
因為parseNum()擴展方法不存在衝突,因此可直接使用。僅當存在擴展類型衝突時,才需要增加前綴。
實現擴展方法
在前面2個章節,其實已經提到了部分擴展方法的實現方法。擴展方法實現語法如下(擴展類型名是可選的):
extension <extension name>? on <type> {
(<member definition>)*
}
如下代碼樣例,對String類型,增加了2個擴展方法(擴展類型名:NumberParsing):
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
擴展類型中的成員,可以是方法、Getters、Setters和操作符,同時也可以是靜態屬性和靜態方法,外圍可通用普通類型靜態屬性和靜態方法一樣使用。
未命名的擴展類型
我們定義未命名的擴展,它們的可見範圍僅在庫內容(類似於私有屬性和方法);由於擴展類型未命名,因此無法明確的用於衝突解決,它們的靜態屬性和靜態方法,也只能在擴展內部使用:
extension on String {
bool get isBlank => trim().isEmpty;
}
實現泛型擴展
擴展也運用在泛型參數,如下代碼樣例,對List<T>增加擴展方法和操作符,類型T在調用時才綁定靜態類型:
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
我的本博客原地址:https://ntopic.cn/p/2023110401