Post

Dart如何實現工廠法

前情提要

所謂的工廠法,在前端設計模式上面算是行之有年,目的是為了達到元件低耦合,做出組件化好管理的Code,那讓我們來看一下Dart是怎麼做的。

模擬http請求情況

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void getAnimalList() {
  const jsonArray = '''
  [{"name": "happy", "type": "dog", "age": 18, "no": 4},
  {"name": "chiBe", "type": "cat", "age": 18, "hobby": "sleeping"},
   {"name": "carry", "type": "cow", "age": 5, "bobby": "eat", "color": "white"}]
  ''';
  /// 這邊模擬非結構化的資料(NoSQL)
  final List<dynamic> dynamicList = jsonDecode(jsonArray);
  /// 當我們發出請求時,後端通常會Response Map類型
  List animalList = dynamicList.map<AnimalModel>((v) => AnimalModel.fromJson(v)).toList();
  /// 在Dart由於管理和可視性,我們都我宣告modal做管理,這邊透過Map的方式將資料結構化
  print(animalList);
  /// output: [Instance of 'AnimalModel', Instance of 'AnimalModel', Instance of 'AnimalModel']
}

建立一個動物的Modal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AnimalModel {
  final String name, type;
  final int age;

  AnimalModel({
    required this.name,
    required this.type,
    required this.age,
  });

  static AnimalModel fromJson(Map<String, dynamic> jsonData) {
    return AnimalModel(
      name: jsonData['name'],
      type: jsonData['type'],
      age: jsonData['age'],
    );
  }
}

關鍵字 factory

Dart有提供factory語法糖,我們將fromJson()改成factory的方式,會發現他們都會回傳一個Instance,但factory不需要額外寫額外的類別,而這也是他們之間的差異,一般函數是可以自訂要返回的類別(或者不要),而factory函數則是綁定返回類別。

1
2
3
4
5
6
7
factory AnimalModel.fromJson(Map<String, dynamic> jsonData) {
  return AnimalModel(
    name: jsonData['name'],
    type: jsonData['type'],
    age: jsonData['age'],
  );
}

Abstract Class

抽象類別的用處在於,他會去限制你的Class必須要有哪些功能(Function),有寫過TypeScript的人應該會聯想到interface(介面),其實他們是同樣的意思,但在Dart當中interface並沒有被定義為關鍵字,每個Class都有Implicit interfaces的特性,在建構時,跟一般的Class一樣,都可以設Constructor(建構子)、Field(欄位)、Method(方法),只是差別在他一定要有抽象方法(只有方法名沒有內容),這邊我直接舉個例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
abstract class AnimalAbstract {

  void eat(); // 每個動物都會吃飯,我把它定義為抽象方法

  void sleep() {
    print('愛睏了,去睡覺');
  }

}

class DogClass extends AnimalAbstract {
  // 可以定義更多變數或是方法
  void run(){
    print('跑起來');
  }

  // 一定要將父類別的抽象方法都實作,不然會報錯,透過override進行覆寫
  @override
  void eat() {
    print('我好乖,今天吃西沙');
  }
}

main() {
  DogClass happy = DogClass();
  happy.run(); // print 跑起來
  happy.eat(); // print 我好乖,今天吃西沙
  happy.sleep(); // print 愛睏了,去睡覺
}

Implements

這邊示範一般的Class如何變成interface,讓子類別只能按照指定方式去建構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Animal {
  final String type;

  Animal(this.type);

  // 每個動物都會吃飯
  void eat(String food) {
    print('$type,只能吃$food');
  }
}

// 為了讓子類別有所限制,我們會加上implements。
class DogClass implements Animal {
  // 當宣告Animal為介面,就要把它本身的方法或屬性都覆寫一遍
  final String name;
  @override
  get type => '小狗';

  DogClass(this.name);

  void eat(String food) {
    print('$name好乖,今晚吃$food');
  }

  // 可以定義更多變數或是方法
  void run(){
    print('跑起來');
  }
}
main() {
  Animal('企鵝').eat('魚'); // print 企鵝,只能吃魚
  DogClass('happy').eat('西沙'); // print happy好乖,今晚吃西沙
}

Mixin

這邊先舉個例子,有些動物會跑步,有些動物會游泳,有時候我們為了管理會把這些屬性獨立出來,那我們來看一下這個是怎麼運作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Animal {
  final String type;

  Animal(this.type);

  void eat(String food) {
    print('$type,只能吃$food');
  } 
}

mixin CanRun {
  void run() {
    print('跑起來');
  }
}


mixin CanSwim {
  void swimming() {
    print('游起來');
  }
}

class FishClass extends Animal with CanSwim {
  String name;
  FishClass(this.name) : super('fish');
}

main() {
  FishClass('尼莫').swimming(); //print 游起來
}

結語

從上面的例子,你可以發現到,當動物種類變多,我們可以運用單一類別來實現不同的功能,而良好的拆分也可以讓偶和度降低,而Abstract的概念也是在告訴我們,會做這些限制並不是平白無故,當Code越來越複雜,之後要再回去找eat到底是誰在吃,會變得困難起來,而在協作上面,他也會增加程式碼的可讀性,以上內容算是一小部分,其他細節可以到官方文件Dart Language閱讀。

This post is licensed under CC BY 4.0 by the author.