類別(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
系列文
待補