Dart 基礎 - 5

Dart Basic - 5

Posted by Bobson Lin on Monday, April 8, 2019

類別(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 (工廠建構子),來實例化;
不過在物件導向中 抽象類別 通常是不可實例化的。


CircleRectangle 中我們是使用 extends,不過使用 implements 也是一樣效果的。


個人感想,抽象類別主要是把概念上的通用性抽象化,也方便規範後續繼承此通用性的類別。

多重繼承(multiple inheritance)

Dart 中只能使用單一繼承,也就是 extends 只能繼承一個類別。

但 Dart 中,有支援 implements (接口類別) 跟 with (混合類別使用)。 這兩個關鍵字可以連接多個類別,概念上可說是多重繼承的延伸。

很多程式語言都有這些特性(像是 Python 和 JavaScript)
這種特性避免了多重繼承的衝突問題,且達到了 解耦合 的功能。

接口類別(interface)

Dart 中並沒有 interface 關鍵字,他是用 abstract classclass 來宣告接口類別。
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> {

可以看到 DateTimenumDurationStringBigInt 均有實現 Comparable 這個接口。

混合類別(mixins)

Dart 中用 mixin 來定義混合類別,使用時用 with
通常都是將 “通用” 的特性獨立出來,寫成一個 混合類別(mixin),也方便覆用在多個地方。

雖然說是混合”類別”,但 @Overridesuper 是不能使用的。
混合類別與接口類別看起來有點神似,但概念還是不同,以下舉個範例。


在近代生活中手機(Phone)是不可或缺的,每個手機有一個自己的號碼(phoneNumber),
可以用來撥打電話(dial)和接聽電話(pickUp),並且還有時鐘及鬧鐘的功能(Clock)。 而現在最常看到類手機的設備有智慧型手機(SmartPhone)、智慧型平板(SmartTablet)。

以上這三行描述,想要實做成程式碼,可以怎麼作呢?

我們可以這樣看,

  • 基本屬性及功能(interface)[Phone] - phoneNumberdialpickUp
  • 附屬屬性及功能(mixin)[Clock] - nowTimesetAlarm
  • 需實現 - SmartPhoneSmartTablet(無撥打電話功能)

基本架構出來了,可以來寫寫程式碼囉~

  • 基本屬性及功能(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");
  }
}
  • 需實現 - SmartPhoneSmartTablet
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 是滿軟體工程中核心的概念。
搞懂也更能體會讀良好軟體設計的樂趣呢~ 😁

你也可以瞧瞧…

參考

系列文
待補