在物件導向語言中,每一個物件都是一個類別,子類別繼承父類別以取用父類別的類別方法與類別屬性。在 Dart 中,每一個物件都是 Object,這代表每一個物件都是繼承 Object 類。
雖然,每一個類別都只能繼承一個父類別,不過 Dart 提供了 Mixin-based 的繼承,可以“混合”多個類別在同一個類別。
那麼,要如何定義一個類別呢?
通常,第一步,我們會建立一個與類別同名的檔案。
例如,我們要建立一個名稱為 Student 的類別,我們會先建立一個 student.dart 的檔案
第二步,在該檔案中,使用關鍵字 class 來建立一個類別。
class ClassName{
...
}
一般在定義類別的名稱時,都是以駝峰的形式來命名。
範例:Student 類別
class Student {
var name = 'Andy';
var age = 30;
void greeting(){
print('Hello, my name is $Andy');
}
}
要使用類別,要先將它實例化才能使用。
範例:實例化 Student 類別。
void main(){
var student = Student();
}
將類別名稱後面加上小括弧,就代表實例化了這個類別。
其中,小括弧代表的是建構式傳入的引數,預設的建構式是沒有傳入引數,所以使用空的小括弧即可。
每一個類別裡,都是由函數 (Functions) 以及屬性 (Properties) 所組成。當外部實例化該類別之後,要怎麼參考該類別裡的成員(函數或屬性)呢?
用點 ( . )來參考成員。
void main(){
var student = Student();
var student_name = student.name;
var student_age = student.age;
student.greeting();
}
如果類別沒有實例化,是不能參考裡面的函數及屬性,
例如:我們未將 Student 類實例化,卻參考了 name 。
void main(){
Student student;
student.name;
}
//Unhandled exception:
//NoSuchMethodError: The getter 'name' was called on null.
此時就會發生異常:NoSuchMethodError: The getter 'name' was called on null。
因為 student 尚未設定值,所以預設是 null,null 是不能參考任何項目的。
在點前方加上一個問號 (?.),代表非 null 時,才參考,否則就跳過。
void main(){
Student student;
student?.name;
}
在上面的範例中,Student 類裡的屬性:name 以及 age,都是固定值,每次參考的時候,都不會有任何改變,我們將這兩個屬性改成由建構式進行初始化。
範例:替 Student 類加上建構式。
class Student{
final String name;
final int age;
Student(this.name, this.age);
void greeting(){
print('Hello, my name is $name');
}
}
範例:實例化含有建構式的 Student 類別
void main(){
var student = Student('Axl', 30);
student.greeting();
}
//Hello, my name is 'Axl'
在實例化時,小括弧裡面直接將數值按照建構式的順序填入。
前面所介紹的為預設的建構式,具有順序、數量一致的特性。當順序不對,傳入的內容就不正確、甚至會編譯失敗。
什麼是具名引數呢?在呼叫建構式建立類別實例之時,不需依照順序、數量,也可以成功的建立,但是,在每一個傳入的建構子的引數,都必須提供屬性的名稱。如此一來,編譯器才會知道由具名建構式帶入的引數,是要指定給哪一個屬性。
定義如下:
ClassName({properties...});
範例:
class Student{
final String name;
final int age;
Student({this.name, this.age});
}
在類別建構子後方的括弧,裡面用大括弧 { } 包起來。
使用範例:
void main(){
var student = Student(name:'John', age: 10);
}
用具名建構式建立類別時,將類別的屬性用冒號帶入傳入的引數,再用逗號分隔不同的屬性。
當類別裡有預設建構子後,可以針對不同的用途建立新的建構式,並用不同的名稱識別。
ClassName.named(parameters){
//set property of class
}
如果我們想針對國小一年級的學生設計類別,且因為每個國小一年級的學生年齡都是8歲,如果使用原本的建構式,就會每次都傳入相同的引數:(age=8),有沒有什麼方法可以固定住某個引數呢?
將前面的範例修改如下:
class Student{
String name;
int age;
Student(this.name, this.age);
Student.first_grade(String name){
this.name = name;
this.age = 8;
}
}
在上方的範例中,具名建構式設定屬性值的動作是在大括弧內進行,我們可以將大括弧改為直接呼叫預設建構式。
class Student{
String name;
int age;
Student(this.name, this.age);
Student.first_grade(String name) : this(name, 8);
}
我們在具名建構式後方加上冒號以及關鍵字 this,這表示要呼叫這個類別裡的建構式。
接者,我們將具名建構式的引數直接傳給預設建構式,再將數字 8 填入第二個欄位。
這樣一來,我們使用具名建構式之後,它就會將值傳給預設建構式,來達到一樣的效果。
在工廠模式中,我們可以產生一個類別的實例化。
工廠模式的關鍵字 factory。
class Student{
String name;
int age;
Student(this.name, this.age);
factory Student.first_grade(String name){
return Student(name, 8);
}
}
上面的範例,在我們每次建立實例的時候,都會指向記憶體的某一塊區域。雖然帶入的引數相同,在執行時,都會被指向不同的記憶體區塊。
有時候,我們希望帶入的引數相同,在建立類別實例時,這些類別實例會被指向在同一個記憶體位置上。
這時候我們可以用關鍵字 const 來定義建構式。
範例:將 Student 的建構式以 const 修飾。
class Student{
final String name;
final int age;
const Student(this.name, this.age);
}
把它實例化看看,並利用 identical() 檢查兩個物件是否相同。
void main(){
var student1 = const Student('Alex', 10);
var student2 = const Student('Alex', 10);
var isSame = identical(student1, student2);
print(isSame);
}
//true
identical 的說明如下:
Check whether two references are to the same object.
建構式,是外部的呼叫者與類別溝通的橋樑,有了建構式才能夠設定類別的屬性。
Dart 提供了四種不同的建構式:預設建構式、具名建構式、工廠建構式以及常量建構式。每一種建構式都有不同的使用情境。
預設建構式是一個類的最基本建構式。
具名建構式可以用在一個類的延伸用法,譬如說某些屬性是固定值。
工廠建構式在每一次呼叫的時候,都會建立一個新的實例,我很常使用在收到 Http request 之後
,解析 Json Object 並實例化物件。
常量建構式用在如果需要在相同的引數下指向相同的記憶體位置時。(表示是完全相同的物件,不是只有屬性相同)
最後,我們還可以用具名引數把帶入的引數加上名稱,讓建構式可讀性增加。