類別(Class) - 下篇
延續上一節的 類別(Class) - 中篇
抽象類別(Abstract class)
在
類別(Class) - 中篇我們有提到抽象方法(Abstract methods),抽象類別裡的函數稱作此。
這次要來深入點講解抽象類別。
簡單來說,抽象類別是定義其他類別的 界面 (interface) (可以想像成樣板),所有 繼承 (extends) 或 實現 (implements) 於此 抽象類別 的類別必須要實作此 抽象類別 裡定義的所有方法。
這樣講完其實還真的是滿 “抽象” 的,所以就看看例子吧~
abstract class Shape {
double get perimeter;
double get area;
String get name;
}以上我們自行定義個抽象類別 Shape,裡頭有三個 Getter 函數,perimeter(周長)、area(面積)、name(描述此形狀)。
接下來我們基於 Shape 來 “實現” Circle 圓形類別。
class Circle extends Shape {
double radius;
Circle(this.radius);
@override
double get area => pi * (radius * radius); // πr²
@override
String get name => "I'm a circle with radius: $radius";
@override
double get perimeter => 2 * pi; // 2πr
}其中 Shape 中定義的三個函數是一定要覆寫的(也就是實現這些三個函數)。
不然會出現 Missing concrete implementations of getter Shape.area, getter Shape.name and getter Shape.perimeter.dart(non_abstract_class_inherits_abstract_member_three) 的 Error。
我們還可以基於 Shape 來 “實現” 不同的形狀,比如 Rectangle 方形類別。
class Rectangle extends Shape {
double length;
double width;
Rectangle(this.length, this.width);
@override
double get area => length * width;
@override
String get name => "I'm a rectangle with length: $length, width: $width";
@override
double get perimeter => 2 * (length + width);
}另外繼承 Rectangle 做出 Square 正方形類別
class Square extends Rectangle {
Square(double side) : super(side, side);
@override
String get name => "I'm a square with side: $length";
}實際上使用這些類別吧~
Square square = Square(10.0);
Rectangle rectangle = Rectangle(20.0, 15.0);
Circle circle = Circle(2.0);
print(square.name);
print(rectangle.name);
print(circle.name);I'm a square with side: 10.0
I'm a rectangle with length: 20.0, width: 15.0
I'm a circle with radius: 2.0雖然 Dart 中的
抽象類別是可以定義factory constructor (工廠建構子),來實例化;
不過在物件導向中抽象類別通常是不可實例化的。
Circle和Rectangle中我們是使用extends,不過使用implements也是一樣效果的。
個人感想,抽象類別主要是把概念上的通用性抽象化,也方便規範後續繼承此通用性的類別。
多重繼承(multiple inheritance)
Dart 中只能使用單一繼承,也就是 extends 只能繼承一個類別。
但 Dart 中,有支援 implements (接口類別) 跟 with (混合類別使用)。
這兩個關鍵字可以連接多個類別,概念上可說是多重繼承的延伸。
很多程式語言都有這些特性(像是 Python 和 JavaScript)
這種特性避免了多重繼承的衝突問題,且達到了 解耦合 的功能。
接口類別(interface)
Dart 中並沒有 interface 關鍵字,他是用 abstract class 或 class 來宣告接口類別。
Dart 中會使用 implements 這個關鍵字來實現接口類別。
Dart 中一個類別可實現多個接口類別。
在 Dart 核心源碼中找了個例子給大家
abstract class Comparable<T> {
int compareTo(T other);
...
}找了 Comparable 這個接口
$ grep "implements Comparable" * -r --color=always | grep -v js
core/date_time.dart:class DateTime implements Comparable<DateTime> {
core/num.dart:abstract class num implements Comparable<num> {
core/duration.dart:class Duration implements Comparable<Duration> {
core/string.dart:abstract class String implements Comparable<String>, Pattern {
core/bigint.dart:abstract class BigInt implements Comparable<BigInt> {可以看到 DateTime、num、Duration、String、BigInt 均有實現 Comparable 這個接口。
混合類別(mixins)
Dart 中用 mixin 來定義混合類別,使用時用 with。
通常都是將 “通用” 的特性獨立出來,寫成一個 混合類別(mixin),也方便覆用在多個地方。
雖然說是混合”類別”,但
@Override跟super是不能使用的。
混合類別與接口類別看起來有點神似,但概念還是不同,以下舉個範例。
在近代生活中手機(Phone)是不可或缺的,每個手機有一個自己的號碼(phoneNumber),
可以用來撥打電話(dial)和接聽電話(pickUp),並且還有時鐘及鬧鐘的功能(Clock)。 而現在最常看到類手機的設備有智慧型手機(SmartPhone)、智慧型平板(SmartTablet)。
以上這三行描述,想要實做成程式碼,可以怎麼作呢?
我們可以這樣看,
- 基本屬性及功能(interface)[Phone] -
phoneNumber、dial、pickUp - 附屬屬性及功能(mixin)[Clock] -
nowTime、setAlarm - 需實現 -
SmartPhone、SmartTablet(無撥打電話功能)
基本架構出來了,可以來寫寫程式碼囉~
- 基本屬性及功能(interface)[Phone]
/// Interface Phone
abstract class Phone {
String get phoneNumber;
void dial(String number) {}
void pickUp() {}
}- 附屬屬性及功能(mixin)[Clock]
mixin ClockMixin {
DateTime nowTime = DateTime.now();
void setAlarm(DateTime alarmTime) {
print("Set an alarm at $alarmTime");
}
}- 需實現 -
SmartPhone、SmartTablet
class SmartPhone with ClockMixin implements Phone {
String _phoneNumber;
SmartPhone(String phoneNumber) : this._phoneNumber = phoneNumber;
@override
void dial(String number) {
print("SmartPhone($phoneNumber) dials $number");
}
@override
String get phoneNumber => _phoneNumber;
@override
void pickUp() {
print("Pick up SmartPhone($phoneNumber)");
}
}
class SmartTablet with ClockMixin implements Phone {
String _phoneNumber;
SmartTablet(String phoneNumber) : this._phoneNumber = phoneNumber;
@override
void dial(String number) {
throw UnimplementedError("SmartTablet can't dial");
}
@override
String get phoneNumber => null;
@override
void pickUp() {
print("Pick up SmartTablet($phoneNumber)");
}
}實際上的使用
SmartPhone myPhone = SmartPhone("123-456-789");
SmartTablet myPad = SmartTablet("111-222-333");
try {
myPhone.dial("789-456-123");
myPhone.pickUp();
myPad.dial("789-456-123");
myPad.pickUp();
} catch (e) {
print(e);
}
print(myPhone.nowTime);
myPhone.setAlarm(myPhone.nowTime.add(Duration(minutes: 20)));Pick up SmartPhone(123-456-789)
SmartPhone(123-456-789) dials 789-456-123
Pick up SmartTablet(111-222-333)
UnimplementedError: SmartTablet can't dial
2019-04-15 20:32:46.581151
Set an alarm at 2019-04-15 20:52:46.581151當然你可以把時鐘當作基礎功能,但因為時鐘功能較為通用,所以我把它獨立出來變成
mixin
終於將 Dart 中類別的部份講完了。 😅
官網中 class 的篇幅雖然不大,但 OOP 是滿軟體工程中核心的概念。
搞懂也更能體會讀良好軟體設計的樂趣呢~ 😁
你也可以瞧瞧…
參考
- Dart 官方文檔 - Classes
- Introduction to Dart for Beginners - Abstract Classes, Interfaces, Mixins, and Casting - Part Five
系列文
待補