---
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。