iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
0
自我挑戰組

從零開始的Flutter世界系列 第 9

Day09 Dart 語言介紹(七) 泛型、Extension

  • 分享至 

  • xImage
  •  

泛型 Generics

讓我們來看看 List 文件,可以看到實際的類型定義為List<E>,這個<..>表示宣告list 是一個泛型(或者參數化)型別。通常我們使用一個字母來代表一個型別引數,例如E,T,S,K 和V 等

為什麼使用泛型?

  • 有很多情況都要使用型別表明自己的意圖,不管是使用泛型還是具體型別。如,如果自己希望List只包含字串物件。則可以定義為List<String>代表一個String的集合資料。這樣可以方便開發工具或者自己的同事可以幫助檢查自己的程式碼是否把非字串型別物件給放到這個list

    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    names.add(42); // 非String,報錯
    
  • 使用泛型的另外一个理由是减少程式碼的重複。泛型可以在多個類型之間分享單個街口和實現。同時還可以繼續使用檢查模式和靜態分析工具提供的程式碼分析功能,例如:創建一個用於緩存快取物件的介面

    //不用泛型,儲存Object
    abstract class ObjectCache {
      Object getByKey(String key);
      setByKey(String key, Object value);
    }
    

    後來發現需要一個用來快取字串的實現,那又要定義一個介面:

    //不用泛型,儲存String
    abstract class StringCache {
      String getByKey(String key);
      setByKey(String key, String value);
    }
    

    後來可能還需要更多的類型,這時候泛型可以省去所有接口的麻烦,創建一個帶有類型參數的介面:

    //使用泛型,則可以省去為每一種類型單獨編寫代碼
    abstract class Cache<T> { //T是一個備用類型,這是類型佔位符,在呼叫該介面的時候會指定具體型別
    T getByKey(String key);
    void setByKey(String key, T value);
    }
    

下面我們來舉幾個泛型例子

  1. 使用集合時

    List 參數化在中括號之前添加 <type>,Map 參數化在大括號之前添加 <keyType, valueType>

    var names = <String>['Seth', 'Kathy', 'Lars'];
    var pages = <String, String>{
    'index.html': 'Homepage',
    'robots.txt': 'Hints for web robots',
    'humans.txt': 'We are people, not machines'
    };
    
  2. 使用建構時

    在呼叫建構式的時候,在類別名字後面使用<..>來指定泛型型別。例如:

    var names = new List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    var nameSet = new Set<String>.from(names);
    
    var views = new Map<int, View>(); //建立了一個key為int,value為View型別的map
    

限制泛型類型

當使用泛型型別的時候,可能想限制泛型的具體型別,我們可以使用extends實現:

class SomeBaseClass{
  //...
}

// T必須是SomebaseClass或其子類別
class Foo<T extends SomeBaseClass> {
  //...
}

class Extender extends SomeBaseClass {
  //...
}

void main() {
  // 使用時可以傳入 SomeBaseClass 或者其子類別
  var someBaseClassFoo = new Foo<SomeBaseClass>();
  var extenderFoo = new Foo<Extender>();

  //也可以不傳入參數,Dart 推斷類型為 SomeBaseClass
  var foo = new Foo();

  // var objectFoo = new Foo<Object>(); //非SomeBaseClass類型或其子類別,報錯
}

泛型方法

T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}

這裡的 first () 泛型可以在如下地方使用參數 T :

  • 函數的返回值類型 (T).
  • 參數的類型 (List<T>).
  • 區域變數的類型 (T tmp).

Extension methods

參考

  1. 當我們使用他人的API 或一些被廣泛實作的Library 時,想要更改API 內容是不切實際、不可能的,但是我們可能仍想添加一些功能

    比如要把一個String 轉成int 的類型,我們原本會直接用api 的方法

    int.parse('42')
    

    但是覺得還要透過int類別來轉型實在太麻煩了,想要簡化成:

    '42'.parseInt() // 無法使用,String 類別沒有提供此api
    

    此時就能透過定義Extension method 來擴充String 類別,添加新的方法

    //在String 類別定義一個extension 名為NumberParsing,並設計我們的方法
    extension NumberParsing on String {
      int parseInt() {
        return int.parse(this);
      }
    }
    
    main() {
      print('42'.parseInt()); // 印出 42
    }
    

    注意:不能對dynamic類型的變數調用擴展方法,例如

    dynamic d = '2' ; 
    print(d.parseInt()); // 會報錯
    

    細節原因可參考:static types and dynamic

  2. 定義完 extension methods 後,可以依定義出來的extension 名,解決使用多個API 時,出現相同的方法名衝突

    如果擴展成員與接口或另一個擴展成員衝突,那麼您可以使用showhide來限制公開的API,從而更改導入衝突擴展名的方式:

    1. hide:

      // 在專案裡,新建另一個Dart 檔string_apis.dart,並在其定義一個String 類別的extension 名為NumberParsing,並設計有方法parseInt(). 
      import 'string_apis.dart' ; // 就像引用外部函式庫
      
      
      // 再新建另一個Dart 檔string_apis.dart2,並在其也定義一個String 類別的extension 名為NumberParsing2,並設計有方法parseInt().  
      //此時兩個api衝突了,使用parseInt() 會有問題,我們選擇hides NumberParsing2 extension method. 
      import 'string_apis_2.dart' hide NumberParsing2 ;
      
      
      // 此時的parseInt() 為在'string_apis.dart' 裡定義的 extension method
      print ('42'.parseInt());
      
    2. show:

      // 一樣有兩個有衝突的外部函式庫
      import 'string_apis.dart' ; 
      import 'string_apis_2.dart' ;
      
      print (NumberParsing('42').parseInt());  //明確的去使用哪個extension 的方法
      print (NumberParsing2('42').parseInt());
      

      如果兩個extension 名字都相同,則需要使用前綴導入

      // 一樣有兩個有衝突的外部函式庫,而且定義在String 類別的extension 名字都為NumberParsing
      import 'string_apis.dart' ; 
      import 'string_apis_2.dart' as rad ; // 需要前綴,好讓後面如果要解決衝突時,能夠使用前綴名去調用它的extension 方法
      
      print (NumberParsing('42').parseInt());  
      print (rad.NumberParsing('42').parseInt()); //透過前綴呼叫extension 方法
      
  3. 不僅可以定義方法,還可以定義其他成員,例如gettersetteroperator

    使用泛型的extension

    extension CustomList<T> on List<T> {
      int get tirpleLength => length * 3;
      List<T> operator() => reversed.toList();
      List<List<T>> split(int at) => <List<T>>[sublist(0, at), sublist(at)];
      List<T> reverseList() => List<T>(this.length)..setAll(0, this.reversed);
      bool equals(List<Object> other) => listEquals(this, other);
      bool listEquals(List self, List other) {
        if (self.length != other.length) return false;
        for (int i = 0; i < self.length; i++) {
          if (self[i] != other[i]) return false;
        }
        return true;
      }
    }
    
    main() {
      List<int> list = <int>[7, 21, 75, 4, 22, 88];
      List<int> listA = <int>[3, 61, 43];
      List<int> listB = <int>[3, 61, 43];
    
      print(list); //印出 [7, 21, 75, 4, 22, 88]
      print(list.tirpleLength); //印出 18
      print(list.reverseList()); //印出 [88, 22, 4, 75, 21, 7]
      print(listA.equals(listB)); //印出 true
      print(list.listEquals(listA, listB)); //印出 true
    }
    

明天將會講解有關於Dart非同步的處理,會是我們關於Dart語言介紹的最後一天!讓我們努力再熬一下吧!


上一篇
Day08 Dart 語言介紹(六) 抽象類別、介面、混合類別
下一篇
Day10 Dart 語言介紹<八> 非同步 Asynchrony support
系列文
從零開始的Flutter世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言