動態

詳情 返回 返回

Flutter/Dart第19天:Dart高級特性之擴展方法(Extension methods) - 動態 詳情

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


Add a new 評論

Some HTML is okay.