Dart 是物件導向的語言,擁有很多物件導向語言的特性。其中,類別 class
可以將變數及函式進行封裝。所謂的物件,就是根據 class 所製造出來的 instance (實例)。由於物件導向本身有一定難度,本次教學只會介紹 Dart 中物件導向的特性和寫法,無法詳盡介紹整個物件導向概念。
如果讀者本身沒有寫過 class
但有寫過 struct
。可以想像 class
就是功能更多的 struct
,除了可以封裝變數之外,也可以綁定函式。另外,在 C 語言中,我們常常會以 malloc()
去產生 struct
,而在其他語言,比如 Java, Javascript 則會使用 new
其實道理是類似的。早期的 Dart 也會使用 new
這個關鍵字去實例化一個物件,但後來發現其實有點多餘,所以新版的 Dart ,可以省略 new
不寫。
在設計 class 時,我們通常會將 class 名稱第一個字母以大寫表示,這個「潛規則」跟 Java 是一樣的。另外,我們未來會提供 public, private 的概念,在 dart 中,若 class 或 class 中的 member 以下劃線
_
開頭,則代表是 private。這種以下劃線代表 private 的潛規則,出現在 Javascript 中,因為早期的 Javascript 無法像 Java 一樣指定 public 或 private。有趣的是,在 Go 語言中,沒有完整的物件導向,當變數的第一個字母為大寫時,代表 public;為小寫時代表 private。每個語言都有自己一套規則!
class Point {
// 略
}
var p1 = new Point(); // 實例化一個 Point 物件
var p2 = Point(); // new 關鍵字可省略
範例程式碼:https://github.com/ksw2000/ironman-2024
官方參考文件:
在實例化一個物件時,比如實例化 Point
時,我們可以呼叫利用 Point()
來實例化,這個作法可以產生一個空的 Point
。在 Dart 中,提供程式設計師設計不同的建構元。比如以下的範例,可以透過 Point.Zero()
建構一個原點座標。
class Point {
double? x;
double? y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
Point.origin() {
this.x = 0;
this.y = 0;
}
}
void main(){
var p = Point(3, 4);
print("${p.x} ${p.y}"); // 3.0 4.0
var q = Point.origin();
print("${q.x} ${q.y}"); // 0.0 0.0
}
另一個常見的例子是 List
,List
有不同的建構元,比如 List.empty()
可以建構一個空的 List()
、List.filled(n, e)
可以一建立 n 個元素 e 的 List。
List<int> list1 = List.filled(5, 1);
List<int> list2 = List.empty();
print(list1); //[1, 1, 1, 1, 1]
print(list2); //[]
建構函式通常會將值直接賦予物件內的成員,由於這個操作太常出現,於是 Dart 提供簡化的寫法。
class Point {
double? x;
double? y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
Point.origin() {
this.x = 0;
this.y = 0;
}
}
// Point2 的寫法與 Point1 相同
class Point2 {
double x;
double y;
Point2(this.x, this.y);
Point2.origin()
: this.x = 0,
this.y = 0;
}
物件導向的語言中,常常會需要對一個值做存取,也許這個值並不真的存在,此時我們就必需自己手動設定 getter 和 setter,如以下範例:
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
double getRight() {
return left + width;
}
void setRight(double value) {
left = value - width;
}
double getBottom() {
return top + height;
}
void setBottom(double value) {
top = value - height;
}
}
void main(){
var r = Rectangle(0, 0, 30, 40);
r.setBottom(50);
r.setRight(40);
print("${r.top}, ${r.left}"); // 10.0, 10.0
}
上面的範例示範了在不使用 getter 與 setter 的情況下的實現方式。我們可以利用 Dart
提供的 getter 和 setter 簡化程式碼。Javascript, Kotlin, Swift 都有類似的語法糖。
class Rectangle2 {
double left, top, width, height;
Rectangle2(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
void main(){
var s = Rectangle2(0, 0, 30, 40);
s.bottom = 50;
s.right = 40;
print("${s.top}, ${s.left}"); // 10.0, 10.0
}
有時,我們會希望使用者自行設計一個包含某些方法的 class。當使用建造完這種 class 後,才方便被後續的程式碼使用。這時,我們可以設計一個模版,稱為抽象類別 abstract class
,這個抽象類別只是告訴使用者要怎麼去設計,而且抽象類別不可以直接被實例化。使用者必需設計一個 class 去 extends
這個模版,並且按照需求去 @override
裡面的方法。假如使用者設計一個 class A
,而 A
繼承 (extends
) 了某一個抽像類別 B
後,此時,class A
的實例的型別,就可以同時是 A
也可以是 B
。「繼承」這個概念以型別來看就類似俄羅斯娃娃,A 繼承 B,B 繼承 C,那麼 A 的實例型別上就可以是 A 或 B 或 C。
abstract class Animal {
void bark();
}
class Cat extends Animal {
@override
void bark() {
print("喵");
}
}
class Dog extends Animal {
@override
void bark() {
print("汪");
}
}
void barking(Animal m) {
m.bark();
}
void main(){
var cat = Cat(); // cat 的型別是 Cat,同時也符合 Animal
var dog = Dog(); // dog 的型別是 Dog,同時也符合 Animal
barking(cat); // barking 吃的型別是 Animal,Cat 符合 Animal
barking(dog); // barking 吃的型別是 Animal,Dog 符合 Animal
}
以上的情境,在 Java 中可以利用 interface
和 abstract class
實現。雖然這兩個用法要解決的事情大致相同,但使用上仍有一些差異,比如一個 class 在實作時,可以實作不同的 interface
,但卻只能繼承一個 abstract class
。而在 Dart
中,我們在此章節會先介紹最簡單的 abstract class
,後續會再提到更進階的操作。
後記:今天出去玩,時間很趕,差點沒寫完。然後我有點擔心教不完了