--- title: Dart 學習筆記 tags: Dart robots: index, follow lang: zh dir: ltr breaks: true GA: UA-4968470-1 --- # Dart 基本概念 [Dart 線上實作平台](https://dartpad.dartlang.org/) :::success + `Dart` 是由 Google 所開發的物件導向程式語言,其開發目的在於比 `javascript` 更有架構性的去設計網頁,又不失其彈性,且 OO 架構的語言特性讓大部分程式設計者可以無痛轉換。 + 相比於`javascript Dart` 對於型態較為嚴謹,不會像`javascript` 可以容忍一些型態上的 Exception,`javascript` 的語言特性也使它不易於開發大型的網頁應用程式,執行效率以及維護成本都是大問題,`Dart`也為此而生。 ::: --- ## 資料型態 ||| |:--------:|:-------:| | var | 如同 javascript 的宣告,為不定的型態 | | Object | 所有型別 class 的父類別 | | dynamic | 與 var 基本相同,不過 dynamic 是由 Object 實作屬於 class,var 只是關鍵字宣告 | | num | int 與 double 的父類別 | | int | 整數 | | double | 雙精度浮點數 | | bool | 布林 | | String | 字串 | | Symbol | ==※待補== | | List<> | 與 Python 的 list 有 87% 像,支持泛型 | | Map<,> | 與 Python 的 dictionary 有 87% 像,支持泛型 | :::warning + Dart 沒有 char 的資料型態,想抓取逐個字元[參考](#debug-整理)。 + Dart 在混入變數輸出時可以使用 $ 作為前綴,以此來插入變數,例如:`print("Hello ${name}")`,也可以此方式連接字串。 + String 的表達方式可使用 `" "` 以及 `' '` 都可以,在前加上 r 如`r" "` `r' '` 可以無視特殊字元的使用。 + 如果想使用動態類別的方法去接收不同的 class 建議使用 `dynamic` ,即便使用 Object 也可以成功執行,但在檢查階段 Object 因為 class 實作方法很少,可能會產生不少警告。 + 做數學運算及使用的時候建議型態宣告為`num`可以幫助自動轉型,避免型態上的錯誤。 ::: ### 常數宣告 :::info Dart 的常數宣告有兩個關鍵字 `final` 與 `const`,兩者之間是有些區別的,以下做簡單介紹: ::: ```javascript= int a = 10; final fin_number = a; const con_number = 10; ``` + 上面示例中 `const` 如果直接`con_number = a`會報錯,因為 `const` 會在編譯過程就給予定值,而 `final` 則是執行過程才給予值。 + 可以想成 `final` 只是一般的變數賦予,但是一旦給予值就不能更改。 + `const` 則比較像直接給定一個絕對不變的常量。 ```javascript= List<int> list = [2,64,33,21,6]; final fin_list = list; const con_list = const[2,64,33,21,6]; fin_list.add(10); ``` + 上面示例是可以執行的,`final`指定的變數依舊是沒變的,因為`final`修飾的變數指向的其實是一個 reference,其包含的內容是可以更改的。 + 而`const`若要賦予 List , Map 這種動態配置的類別的時候,需要加上`const`的修飾,表明這是一個不可修改的常量。 --- ## 函式 Function :::info Dart 的所有函數,皆由 Function 這個類別實作,所以有許多特性符合 class 的概念,甚至可以將 Function print 出來看它的結構,首先示範簡單的 Function 宣告: ::: ```javascript= void HelloName(String name){ return print("Hello ${name}"); } ``` + 回傳型態不一定要進行宣告,若沒有宣告則會自行根據回傳值代入。 + 若是像示例的簡單回傳函式則可簡寫為: ```javascript= void HelloName(String name) => print("Hello ${name}"); ``` + Dart 也支援匿名函式的寫法: ```javascript= var HelloName = (name) => print("Hello ${name}!!"); HelloName("Bobo"); ``` + 如上面所說函式是由 Function 類別實作,因此能夠以動態類別來承接。 + 也可以做成函式閉包( Closure ) ```javascript= Function adder(num a) { return (num b) => a + b; } var addThree = adder(3); print(addThree(5)); ``` + 上面示例會得到8的輸出 + Dart 沒有 java Overloading 的用法,但它支持可選參數的用法,如下: ```javascript= String customFunction(int a,{int b,String c="haha",int d = 10,int e}){ print("a:$a | b:$b | c:$c | d:$d | e:$e"); } customFunction(100,e:20,c:"hello"); ``` + 用`{}`把可選參數包起來,以上輸出為`a:100 | b:null | c:hello | d:10 | e:20`,未選擇設定的參數則代入預設值,參數設定不需要按照順序,但要標明參數名稱。 + 可選參數的用法還有: ```javascript= String customFunction(int a,[int b,String c="haha",int d = 10,int e]){ print("a:$a | b:$b | c:$c | d:$d | e:$e"); } customFunction(100,0,"123"); //a:100 | b:0 | c:123 | d:10 | e:20 ``` + 若將函式宣告改成`[]`則必須按照順序填入,且不能跳過中間參數設定,也不必標記參數名稱,按順序輸入即可。 ### Typedef + 跟 C 語言的 typedef 不同,Dart 中主要用來定義 function 的類型: ```javascript= typedef num Compare(num a,num b); Compare compare = (num a,num b) => a-b; compare(3,1);//2 ``` + 有點像是去幫這個類型的 function 取一個代號。 + 像是用在[建構子](#constructor),為 class 指定一個方法進去使用: ```javascript=+ class TestType{ Compare compare; TestType(this.compare); } num add(num a,num b) => a+b; class main{ TestType test = new TestType(add); test.add(3,4);//7 } ``` + 如此可以指定物件擁有客製化的 function 外,還能加上一些設定上的限制。 --- ## 類別 Class ```javascript= import "dart:math"; class Point{ num _x;//private variable num _y;//private variable void set x(num x){ _x = x ;} void set y(num y){ _y = y ;} num get x => _x; num get y => _y; Point(this._x,this._y); //public method num distance(Point other) => sqrt(pow(x-other.x,2)+pow(y-other.y,2)); } ``` :::warning + Dart 的可視權限宣告不像 java 這麼複雜,也不須加前綴詞,變數名稱前加上 `_` 即宣告為 private 除此之外都是 public。 ::: + constructor 的設計可以用簡寫的方式表達,如上面的示例原本應寫成`Point(int x,int y){_x = x; _y = y;}`。 + 物件導向中封裝概念的 setter 與 getter 也有關鍵詞可以使用。 ```javascript= Point a = new Point(0,0); Point b = new Point(0,0); b.x = 3; b.y = 4; print("x:${b.x}y:${b.y}");//x:3,y:4 print(a.distance(b));//5 ``` + 如果有設置 setter 或者 getter 調用的時候不需要像是使用函式一般加上`()`,當作是一般變數`.`後加上字段就會自己呼叫 setter 與 getter 了。 ### Static + Dart 也可在類別裡設定靜態變數以及靜態函式 ```javascript= class Math{ static num PI = 3.14159; static num pow(num x,int n){ num tmp = 1; for(int i=0;i<n;i++) tmp*=x; return tmp; } } ``` + 這類的方法及變數屬於 class 本身,不必特別實作成物件即可調用 ```javascript= print(Math.PI); print(Math(2,10)); ``` ### Constructor + Dart 的建構子可以像 java 一樣在類別內根據 className 定義 ```javascript= class Point{ num x; num y; Point(num x,num y){ this.x = x; this.y = y; } } ``` + 但是 Dart 並不支持 Overloading 的用法,所以如果需要使用多種建構子,可以使用 Dart 的特有寫法 className.identifer: ```javascript= import "dart:math"; class Point{ num x; num y; Point(this.x,this.y); Point.zero() : this.x = 0 , this.y = 0; Point.polar(num radius,num thita){ //極座標表示法 this.x = radius*sin(thita); this.y = radius*cos(thita); } void printLocation(){ print(x.toStringAsFixed(0),y.toStringAsFixed(0)); } } ``` + 上例的 `Point.zero()` 也可以寫成 `Point.zero():this(0,0);` ,去調用其他的建構子。 + 使用上跟一般建構子一樣,在 new 的階段呼叫: ```javascript=+ Point a = new Point(4,3); Point b = new Point.zero(); // r = 5 角度 53 度 Point c = new Point.polar(5,PI/2*(53/90)); a.printLocation(); b.printLocation(); c.printLocation(); ``` + `toStringAsFixed` 用於轉 String 同時取 round 到想要的位數,上例輸出 a 與 c 點的值應該會一樣。 + 要注意一下,sin 以及 cos 的參數值是以 math API 中的 PI 為 180 度的標準,所以需要用數學方法轉換成[徑度](https://zh.wikipedia.org/wiki/%E5%BC%A7%E5%BA%A6)表示。 ### Super Constructor + Dart 中子類別的建構子一定要調用父類別的建構子,若無寫明,預設調用無參數的建構子,若已定義父類別的建構子,則強制要呼叫父類別的建構子,並代入參數: ```javascript= class Person{ String sex; String name; final int birth_year; Person(this.name,this.sex,this.birth_year); } class Employee extends Person{ String job; Employee(String name,String sex, int birth_year):super(name,sex, birth_year); Employee.getJob(String name,String sex, int birth_year,String job):super(name,sex,birth_year){ this.job = job; } } ``` + 雖然強制建構子要呼叫父類別建構子,但是 Dart 的繼承並不會繼承建構子,即是你無法在其他地方使用 super 的建構子語法。 + 在建構子中位於 `:` 以及 `{}` 中間的區段為**初始化列表**,也就是說可以在 `{}` 中的建構子執行前,搶先初始化變數,當然也可以用來調用 super。 ### Factory Constructor ```javascript= class Symbol { final String name; static Map<String, Symbol> _cache; factory Symbol(String name) { if (_cache == null) { _cache = {}; } if (_cache.containsKey(name)) { return _cache[name]; } else { final symbol = new Symbol._internal(name); _cache[name] = symbol; return symbol; } } Symbol._internal(this.name); } ``` + Dart 支持 factory 前綴詞來宣告一個 factory,讓 constructor 允許回傳一個自身的類別,示例的用法可以用來確認創造的物件是否重複,如重複了則調用先前創建的。 :::warning 代補 ::: ### Override Operation :::info 如題,Dart 的 class 有個有趣的設定,就是可以自定義運算子對 class 的操作,這種用法可以幫助我們更直覺地去使用這些類別,首先先整理出 Dart 的運算符號: ::: ||| |:--------:|:--------:| |`! , ~`| 求補數 | | `* , / , % , ~/ , + , -` | 乘 , 除 , 求餘數 , 求商 , 加 , 減 | | `<< , >>` | 位元左移 , 右移 | | `> , < , >= , <=`| 大小判斷 | | `as , is , is!` | `as`用來轉換類別,`is`則用來判斷類別 | | `== , !=` | 相等判斷 | | `& , ^ , |` | bitwise 運算 AND , XOR , OR | | `&& , ||` | 邏輯判斷 AND , OR | |` n!=null ? i : k; `| 判斷式,等價於`if(n!=null) return i; else return k;` | + 並非所有運算子都可以 override ,可以 override 的運算子如下` +, -, *, /, ~/, %, [], []=, <, >, <=, >=, ==, &, ^, |, ~, <<, >>` + Override 的方式就像是撰寫一個函式一樣: ```javascript= class Point{ num x,y; operator -(Point other) => new Vector(x-other.x,y-other.y); bool operator ==(other){ if(other is Point){ return x==other.x && y==other.y; }else return false; } } class Vector{ num x,y; Vector(this.x,this.y); operator -(Vector other) => new Vector(x-other.x,y-other.y); operator *(Vector other){ return x*other.x+y*other.y; } bool operator ==(other){ if(other is Vector){ return x==other.x && y==other.y; }else return false; } } ``` + 依照數學定義來覆寫 - 以及 * 的定義,可以更直覺也更方便的去使用這些類別。 + 要注意的是,覆寫 == 運算子的時候參數設定不需要給予型態,但是函式定義型態必須是 `bool`。 + 另外調用的覆寫運算子是調用左邊物件的。 --- ## 非同步問題 :::success Dart 在處理非同步問題的時候通常使用`async`以及`await`的表達式,而這類問題在 Dart 中的返回類別通常是`Future`或`Stream`兩種類別。 ::: ### Future :::info 顧名思義,這個類別表達的是說在未來會給你一個結果,因此呼叫 Future 類別的 function 時,function 會立刻回傳一個 Future 類別給你,程式則繼續向下執行,等到 function 執行完才會回傳該有的東西給他,示例如下: ::: ```javascript= class MainClass{ Future<int> factorial(int n) async{ int tmp = 1; for(int i=2;i<=n;i++) tmp*=i; print("I'm Future"); return tmp; } Future n = factorial(4); print("Class:$n"); n.then((n) => print("factorial(4):$n")); } ``` + 上面示例中先後輸出為`Class:Instance of '_Future'` => `I'm Future` => `factorial(4):24`,表示出呼叫 Future 類別的執行順序,若要等待 Future function 執行完畢可調用 Future 的方法 then ,以 CallBack 的方式調用。 :::warning + 注意 Future function 一定要標註 async 的關鍵字才能夠回傳 Future 的類別。 + 且 Future 需指定泛型類別才可以得知 function 執行完的回傳類別。 ::: + 有時候為了方便使用,不想用 then 的方式再去寫 CallBack Function 可以使用 await 來承接被解開的 Future function: ```javascript= class MainClass async{ Future<int> factorial(int n) async{ int tmp = 1; for(int i=2;i<=n;i++) tmp*=i; print("I'm Future"); return tmp; } int n =await factorial(4); print("factorial(4):$n"); } ``` + 這樣就可以用熟悉的方式去承接 function 的 return 值,但要注意這裡會先輸出`I'm Future`再輸出 n 的結果 :::warning + 注意使用 await 關鍵字時 class 或 function 需要被 async 標記才可以成功調用。 ::: ### Stream :::warning 待補 ::: + 使用上有點像 C 的 pointer 而 Stream 就是 Future 的 pointer ,在 Dart 中則是被當作疊代來使用,配合上`await for`: ```javascript= Stream<int> countStream(int n) async*{ for(int i = 1; i <= n; i++) { yield i; } } await for(int i in countStream(10)){ print(i);// print 1 2 3 ... 10 } ``` --- ## Debug 整理 ```javascript= var a = 1; assert(a==2); print(a); print(a.runtimeType); ``` `assert` 判斷參數的判斷布林,若為非則中斷程式。 `runtimeType` 則可以輸出變數型態,對於觀察 API 的使用很有幫助。 + String 常用方法: ```javascript= String str = "hello world"; print(str[0]);// h print(str.indexOf("o"));// 4 //轉換型態 String dble = "1.1"; print(double.parse(dble)); String intStr = "10"; print(int.parse(intStr)); ``` + Number 常用方法 ```javascript= num Numbers=3.14159; print(Numbers.toStringAsFixed(2));// 3.14 print(Numbers.toString())//3.14159 print((-Numbers).abs());//3.14159 ``` --- # DTO in Dart :::info DTO 全名為 Data Transfer Object 意旨將資料格式轉換成程式中的物件來實作,方便程式調用及 Debug。 ::: + DTO 的簡單實現 ```javascript= class Store{ String name; String desc; String address; mapToObject(Map map){ this.name = map["name"]; this.desc = map["desc"]; this.address = map["address"]; } } main(){ Store tmp = new Store(); Map map = {"name":"麥當勞","desc":"速食店","address":"A市B區C路D號"} tmp.mapToObject(map); } ``` + 上面是一個簡單的 DTO 作法,但是這種作法既麻煩也難以維護,一但要擴充參數或者傳遞其他格式的參數,需要更動的程式碼會很複雜。 + 然而 DTO 也實在符合我們的需求,因此在 Dart Package Manager 中已經有可使用的套件了。 + [Dartson](https://pub.dartlang.org/packages/dartson) 裡面提供各種方法幫助我們做到 DTO 的實現。 --- ## Dartson ### 安裝 Dartson : + 更改 pubspec.yaml 設定檔 ```javascript= dependencies: dartson: "^0.2.7" ``` + 鍵入命令行` $ pub get `取得 package + 使用時 import ```javascript= import 'package:dartson/dartson.dart'; ``` ### Dartson 實例 ```javascript= class Store{ String name; String desc; String address; } main(){ var _dson = new Dartson.JSON(); Map map = {"name":"麥當勞","desc":"速食店","address":"A市B區C路D號"}; Store tmp = _dson.map(map,new Store()); } ``` + 這樣就可以簡單做到 DTO 了,不用額外設計 function 。 ```javascript= main(){ var _dson = new Dartson.JSON(); List list = []; Map mcd = {"name":"麥當勞","desc":"速食店","address":"A市B區C路D號"}; Map kfc = {"name":"肯德基","desc":"速食店","address":"D市C區B路A號"}; list.add(mcd); list.add(kfc); List<Store> tmp = _dson.map(list,new Store(),true); } ``` + 後面的 true 參數,表示用來 maping 的參數是一個 List。