今天會介紹Dart的class以及常見的關鍵字。
class(類別)這個概念出現在許多物件導向的語言如Java中,JavaScript也早在ES6引入。
在本次的flutter專案,我們主要透過建立class來歸納各類型的資料,在class中的欄位(field)儲存值,並透過建立好的操作方式(method)來處理及使用資料。
好的,那我們就開始吧!
結構上大致可以分為
先來看看Dart的class大概長什麼樣子:
lass Person {
String name;
int age;
Person(this.name, this.age);
String get getName => name;
set setName(String newName){
name = newName;
}
void selfIntroduce(){
print('Hi! I am ${name}.');
}
ar person1 = Person('John', 20);
erson1.selfIntroduce(); // Hi! I am John.
``
上,我們建立了一個`Person` 類別。
首先我們定義這個類別有兩個field:`name`和`age`,也就是屬於這個類別的狀態/資料。
接著我們在`Person(this.name, this.age)`定義要建立一個Person的實體 (Instance) 需要輸入兩個參數:name和age,以存入新創建的實體中的field。可以看到下面的 `var person1 = Person('John', 20)`,就是用Person class constructor建立了一個實體person1。
另外定義了name的getter: `getName`,在實例化後使用`person1.getName`就可以得知此人的名字是John。也定義了setter: `setName`,發現輸入錯了還可以用`setName`來改變field name的值,幫John改名,比如說
```dart
print(person1.getName); // John
person1.setName = '鮭魚John';
print(person1.getName); // 鮭魚John
```
然後定義了一個這個類別內建的方法:`selfIntroduce`,任何繼承這個類別或從這個Person建立的實體都會含有`selfIntroduce`自我介紹的功能。
最後我們建立了一個`Person`的實體:`person1`,可以看到他的名字叫做John,年齡20歲,也開朗地就跟大家打招呼!
field 是Class內的欄位,儲存屬於此Class的資料
lass Building {
int roomNum;
int floors;
House(this.roomNum, this.floors);
ar skyscraper = House(500, 50);
``
比如說,我們定義一個`House`類有房間數`roomNum`和樓層數`floors`兩個field,這兩個都是`House`的特質,然後創建了一個instance : `skyscraper`,這個`skyscraper`的狀態是500 `rooms`, 50 `floors`。
建構函式,要建立一個Class的實例時會透過建構函式的規範,比如說上圖的Person class:
lass Person {
String name;
int age;
Person(this.name, this.age);
``
定了建構Person的實體需要哪些資料為field初始化,這邊`Person(this.name, this.age)`指名要用Person constructor就需要name 和age兩個參數
如果我今天想要用Person class 建構不同的實體呢?比如說今天要建構30歲的Person (age = 30),只需要輸入name就好,在其他語言如Java可能會用overload的方式
// Java
lass Person {
String name;
int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
Person(Strin name){
this.name = name;
this.age = 30;
}
erson person1 = new Person('John', 30); // 建立30歲的John
erson person2 = new Person('Doe'); // 建立30歲的Doe
``
是在dart沒有overload,可是它提供named constructor的方式:
``dart
// Dart
lass Person {
String name;
int age;
Person(this.name, this.age)
Person.thirtyYearOld(String newName){
this.name = newName;
this.age = 30;
}
ar person1 = Person('John', 30);
ar person2 = Person.thirtyYearOld('Doe');
``
上,透過`constructor.[name]`可以在同一個類別中自定義各種不同的建構方式
method 是Class之中定義好的方法,可以事先寫好,在實例化之後就可直接取用。先寫好的method預先封裝了一些邏輯,代表這個Class自帶的操作行為
比如說人這個類別會吃東西,任何屬於人類的實例都會吃東西
lass Person {
void eat(){
print('Yummy!');
}
ar person1 = Person();
erson1.eat() // Yummy!
用在資料上,比如說我們定義使用者這個類別有一個功能是抓使用者自己的最新資料
lass User {
// user details
void fetchUserDetail(){
// call api
// udpate user details
}
ar currentUser = User(...);
urrentUser.fetchUserDetail();
``
這個例子中可以看到,User類別自帶取得使用者細節的功能,建立實體之後可以直接抓取資料更新該實體內的field value
用於繼承其他class,一個class只能繼承一個對象。A extends B
代表 「A 是B的概念的延伸」,所以A會包括B class的fields和methods,但是不包括B的constructor:
lass Animal {
void breathe(){
print('Animal Breathe');
}
void grow(){
print('Animal Grow');
}
void move(){
print('Animal Move');
}
lass Dog extends Animal {
void grow(){
super.grow(); // 調用Animal的grow方法
print('Dog Grow');
}
@override // 覆蓋Animal 繼承過來的move方法
void move(){
print('Dog Move');
}
void bark(){
print('Dog Bark');
}
ar cuteDog = Dog();
ar normalAnimal = Animal();
uteDog.breathe(); // Animal Breathe
uteDog.grow(); // Animal Grow Dog Grow
uteDog.move(); // Dog Move
ormalAnimal.move(); // Animal Move
uteDog.bark(); // Dog Bark
``
- 如上例,若`Dog extends Animal`(Dog類別繼承了Animal的類別),則稱Dog為子類,被繼承的Animal為父類。
子類調用父類的同名method 使用 `super.[methodName]`
子類繼承但覆寫父類的method使用`@override`,於是子類可以實踐自己的同名method,但不會影響父類定義好的method
用於實作其他abstract class或者class,一個class可以實作多個對象。A implements B
代表「A 實作了 B 的概念」。
在dart 中,class同時具備了定義介面的功能。所以若A class implements B class
,B會被視為一個實作的規範,A會照 B 所規範要有的內容(field, methods...等)來建立自己的實作,而不是直接繼承B裡面實作好的field和method。以大門(Gate)實踐了門(Door)的介面為例:
lass Door {
void open(){
print('Open the Door!');
}
lass Gate implements Door {
@override
void open(){
print('Open the Gate!')
}
``
上圖,Door有「開」的功能,Gate implements Door,Gate也要實踐自己的「開」是怎麼開的。
abstract class是不能被實例化的class,只能被implement,用意是定義出一個概念的規範,而主要不是用於實作instance,例如車輛(Vehicle)的abstract class:
bstract class Vehicle {
void drive(); // abstract method.
lass Car extends Vehicle {
@override
void drive(){
print('I Drive for My Family!')
}
lass Truck extends Vehicle {
@override
void drive(){
print('I Drive for Goods!')
}
``
混用其他的class,一個class可以混用多個對象。 A with B 代表 「A 混用 B 的概念」。
使用A with B
的目的是在「A在非繼承於B的狀況下,也可以重複使用到B內部實作好的field或method」
比如說我們知道任一個class能extend 的數量只有一個,那我還想用其他class其他寫好的方法怎麼辦呢? 試舉例「病毒是非生命,但也具有生命的特質」:
class NonLiving {
void stayStill(){
print('');
}
}
class Living {
void reproduce(){
print('I can reproduce!');
}
}
class Virus extends NonLiving with Living{
// ...
}
var coronavirus = Virus();
coronavirus.stayStill(); //
coronavirus.reproduce(); // I can reproduce!
你可能會覺得,既然已經extend
NonLiving了,那我就把Living給Virus implement
不就可以讓Virus和Living扯上關係啦?
可是如此就是把Living內部定義的reproduce視為一個規範,我們就要在Virus裡面自己再實踐一次reproduce method;規範有一種但是實踐可以有無數種,就沒辦法達成將一個Living class裡面已經實作好的print('I can reproduce')
直接給Virus使用的效果了。
為了讓定義好的實作能重複使用,這時候就來使用我們的with
功能吧~
extends
, with
, implement
可以連用,不過順序是 extends
> with
> implement
lass A extends B with C,D implements D,E,F
``
將Class內的field或method轉為全域變數,存在Class之中。
使用時只要調用Class.field/Class.method 即可取用,不必將class實例化
class Math {
static double PI = 3.1415926
static double floor(double originNum){
// do math operation
}
}
print(Math.PI) // 3.1415926
print(Math.floor(3.2)) // 3.0
指名此field一旦塞入值初始化之後,便不可再用setter變更
比如說,我們創建一個罐頭(Can)類別,規定在罐頭裡塞進內容物之後就不能改變。這邊舉例拿鳳梨塞進Can裡作成pineappleCan:
lass Can {
final String content;
Can(this.content);
String get getContent=> this.content;
ar pineappleCan = Can('pineapple');
``
於`final`定義content初始化後不能改變,食品既已售出概不退回,不能送回原廠重新裝填了。
果我們硬是對`final`的field設定`setter`意圖改寫這個`content`,想將鳳梨罐頭送回去改塞蘋果
``dart
lass Can {
final String content;
Can(this.content);
String get getContent=> this.content;
set setContent(String newContent){
this.content = newContent;
}
ineappleCan.setContent ='apple';
``
得到以下錯誤,說明已經填好的`final content`並不能被`setter`更改
``dart
content' can't be used as a setter because it's final.
代表暫時不初始化field,除非使用setter
賦予該field才有值。開發者可能基於特殊的考量,在實例化時暫時不想指派值給field;但由於late
field在未初始化時若是被取用可能造成錯誤,務必謹慎使用。
lass Building {
late int doorNum;
int get getDoorNum => doorNum;
set setDoorNum(int num){
this.doorNum = num;
}
ar preSaleHouse = Building();
rint(preSaleHouse.getDoorNum); // 使用getter取值時由於doorNum還未初始化,出現error
reSaleHouse. setDoorNum = 1;
rint(preSaleHouse.getDoorNum); // 賦予值之後doorNum有值了,因此得到 1
``
上例中在doorNum未初始化前就使用getter 取值會出現以下報錯:
``dart
ncaught Error: LateInitializationError: Field 'doorNum' has not been initialized.
``
在物件導向語言中,class內部的資源field 和method通常會有private/public的概念
一般來說,在dart class裡面的field不必透過getter/setter,外部可以自由取用 (public)
lass Person {
String name;
ar person1 = Person('John');
rint(person1.name); // John
``
若是要設定某個field只能在class內部取用(private),只需要在field前面加上_
。要注意的是,dart定義的private field不是限於Class內部,而是Class 所在的file皆可任意使用,若是要在其他file上取用該field必須透過一層getter/setter。以Person class為例:
/ file a.dart
lass Person {
String _name;
Person(this._name);
String get getName => _name;
set setName(String newName){
_name = newName;
}
ar person1 = Person('John');
rint(person1._name); // John
rint(person1.getName); // John
``
``dart
/ file b.dart
mport 'a.dart'
ar person2 = Person('John');
rint(person1.getName); // John
``
今天我們看到了:
extends
: 繼承(包含)其他classimplement
: 實踐其他class定義的功能with
: 在非繼承的情況下重複使用其他class已實踐的fields和methodsstatic
: 宣告為靜態資源,可以直接從Class中調用不必實例化final
: 初始化後不能再更動值late
: 因特殊原因創造實例時不初始化field值,但是取值時需特別小心學會了怎麼寫Dart,明天開始進入flutter的環節,一起來安裝flutter的開發環境吧!