iT邦幫忙

2023 iThome 鐵人賽

DAY 4
1

什麼是物件導向?

物件導向這個用詞的重點在於「物件」兩個字,簡單來說就是將資料、相同的邏輯都包裝在一個「物件」當中。因此一個程式之中可能會有數個不同的物件,這些物件互不影響,就好像一台台獨立的機器在程式中運作一樣。聽起來有點抽象,我們從簡單的例子開始說起。

物件導向是一個很大的概念,我沒有辦法在有限的篇章中完全提及,我們會專注在「語法」的特性上進行介紹!

Class

在成為一個物件之前,我們需要先定義 class (類別)。class 是定義一件事物的模板,比如每個人一定會有名字、年紀、性別這些基本資訊;物件則是根據類別所定義的模板去創建instance (實例)。
在創建一個實例的會需要呼叫一個稱作 constructor (建構子) 的函式,並且函式名稱會與 class 的名稱相同。我們從下面例子來看

// 定義一個抽象的 class
class Person {
  // 每個 Person 有以下三種屬性
  String name; // 名字
  int age; // 年紀
  bool isMale; // 性別

  // Constructor (建構子)
  // 標準寫法
  Person(String name, int age, bool isMale):
    this.name = name,
    this.age = age,
    this.isMale = isMale;
  // 簡化寫法 (建議使用此寫法)
  Person(this.name, this.age, this.isMale);

  // Method (方法)
  void printName() {
    print('My name is ' + name);
  }
}

void main() {
  // 經由建構子將對應的資訊生成物件
  var p = Person('Micro Jordan', 25, true);
  // 取用物件屬性
  print(p.name);

  // 取用物件方法
  p.printName();
}

使用上述例子的 constructor 須確保呼叫建構子的順序為先 nameageisMale

Named Parameters

當我們的屬性有一大堆要初始化時,要這樣一一對照順序真的很容易搞混,又因為 constructor 本身也是個函式,因此可以使用我們前面在 function 提到的 named parameters ,如此在創建一個新物件時便可以更加的容易操作。

class Person {
  // -- 屬性略過 --
  Person({required this.name, required this.age, required this.isMale});
  ...
}

void main() {
  var p = Person(name:'Micro Jordan', age: 25, isMale: true);
}

Named Constructors

具名的建構子,可以讓建構一個物件是更具可讀性。

class Person {
  // -- 屬性略過 --
  // Named Parameters
  Person({required this.name, required this.age, required this.isMale});

  // Named Constructors
  // 建構確定生理性別為女性的人時,可以確定生理性別一定不為男性
  Person.whoIsFemale(name, age) : 
    this.name = name, 
    this.age = age, 
    this.isMale = false;
}

void main() {
  var f = Person.whoIsFemale('Amy', 25);
}

關於建構子還有很多種形式,我們就先不一一介紹拉。
在很多程式語言中會有相對於建構子存在的解構子(destructor),當該物件不需使用時呼叫解構子來釋放資源。在 Dart 中並不提供直接解構的方法,而是透過自動回收機制 (garbage collection)來自動管理物件的生命週期,並自動釋放不再需要的資源。

Methods

Methods 是物件提供物件進行某些操作的函式,例如我們上面的Person 物件的例子中實現了一個 printName 的方法就是用來印出當前物件的人名。我們可以任意的去寫我們所需的 method。

此外Dart 還提供我們去實作 operators 的操作,假設我想要判斷兩個 Person 誰的年紀比較大時,我們就可以去覆寫 > 運算子,同樣我們也舉例來說明。

class Person {
  // -- 屬性與建構子略過 --

  // 因為我可能會跟任何物件比較,因此傳入 Object (所有型態始祖),並於判斷式中先判斷是否為 Person class 再進行判斷年紀屬性
  @override
  bool operator >(Object other) =>
    other is Person && this.age > other.age;
  
}

void main() {
  var p1 = Person(name: 'Andy', age: 25, isMale: true);
  var p2 = Person(name: 'Amy', age: 24, isMale: false);
  // 因為 25 > 24,所以邏輯為真
  print(p1 > p2);
}

註記:如果要覆寫的是 == 這個運算子,則必須連同 hashCode 的方法也一同覆寫

Getters & Setters

在 Dart 中可使用 getter 與 setter 來取得/修改類別的屬性,下面舉個例子就可以明白拉。

class Person {
  // -- 屬性與建構子略過 --

  // getter 方法:出生的年份毋須再宣告成一屬性,因為可以實時的運算取得
  int get bornYear {
    return 2023 - age;
  }

  // setter 方法:一旦過了生日,就年長一歲
  set birthday(bool isAfter) {
    if (isAfter) {
      age += 1;
    }
  }
}

void main() {
  var p = Person(name: 'Micro Jordan', age: 24, isMale: true);
  // 取用 getter
  print(p.bornYear);
  // 假設今年已經過了生日,調用 setter 方法
  p.birthday = true;
  // 從 24 -> 25
  print(p.age);
}

Extends

Extends 可以用來擴充類別,並且繼承原先 class 中的所有屬性及方法。但仍有可能遇到方法需要進行覆寫的情況,因此需要加上 override 的關鍵字。

class Person {
  String name;
  int age;
  bool isMale;

  Person({required this.name, required this.age, required this.isMale});

  void printName() {
    print('Hello my name is ' + name);
  }
}

class Student extends Person {
  String school;

  // 由於 name, age, isMale 皆繼承自父類別,因此呼叫父類別的建構子,再加上新的屬性 school
  Student({required String name, required int age, required bool isMale, required this.school}) : 
    super(name: name, age: age, isMale: isMale);

  // 覆寫 printName 這個 method
  @override
  void printName() {
    print("Hello my name is $name and I'm from $school school.");
  }
}

void main() {
  var p = Person(name: 'Andy', age: 25, isMale: true);
  
  var s = Student(name: 'Andy', age: 25, isMale: true, school: 'ABC');
  p.printName(); // Hello my name is Andy
  s.printName(); // Hello my name is Andy and I'm from ABC school.
}

Abstract class

抽象類別是一個特殊的存在,只有定義類別,而不能實例化。通常是用於定義一個共享的介面或是基礎類別時而使用的。若有 class 要引用 abstract class 則加入 implements 關鍵字表示要實作該抽象類別。

abstract class Speak {
  // 定義一個 method,每個引用的 class 都得實作
  void sayGoodbye();
}

class Person implements Speak {
  // -- 屬性與建構子略過 -- 
  @override
  void sayGoodbye() {
    print("I'm $name, see you next time");
  }
}

class Student extends Person implements Speak {
  // -- 屬性與建構子略過 -- 
  @override
  void sayGoodbye() {
    print("I'm $name from $school, see you next time");
  }
}

class 的概念在 Dart 中真的非常重要,真的認真講起來會花很多篇幅可能才有辦法一一解釋清楚。因此與其一一講清楚,倒不如我們先從簡單例子下去了解大概某個概念的用法為何,先有初步了解後,從實戰中去學習,我覺得更加重要!

接下來我們來淺談 Dart 的錯誤處理。

錯誤處理

Exceptions 表示某種未預期的錯誤或異常情況發生時,用於提供關於這些錯誤中有用的資訊。倘若這些錯誤沒有被處理時,很可能會導致程式的終止,想必這並不是使用者所樂見的情形,因此錯誤處理相當重要。
在 Dart 中可以使用 try 來包住可能引發異常的程式碼,catch 來捕獲以及處理這些異常情況。finally 用於無論 try- catch 中成功或失敗都應執行的程式碼

try {
  // 可能引發異常的程式碼
  // 除以 0 是會引發異常的行為
  int n = 1 / 0;
} catch (err) {
  // Unsupported operation: Result of truncating division is Infinity: 1 ~/ 0
  print('錯誤: $err');
} finally {
  print('無論如何都會執行 finally,通常用於清理的操作');
}

在 Dart 中已經定義了數種 Exception 的形式,我們也經常會用到 throw 去引發自定義的一些異常行為,舉例來說

try {
  if (age < 0) {
    // 當給定一個年齡值為負數時,就拋出錯誤並提供有用的資訊
    throw Exception('年齡不得為負數');
  }
} catch (e) {
  print('錯誤: $e');
}

當然你也可以自定義想要的 Exception class 將這些錯誤進行分門別類

// 自定義一個狀態碼的 exception
class HttpCodeException implements Exception {
  final int statusCode;
  final String message;

  HttpCodeException(this.statusCode, this.message);

  // Exception 為 abstract class 有定義 toString,因此一定要 @override
  @override
  String toString() {
    return 'HttpCodeException: code ' + statusCode + ', Message: ' + message;
  }
}

void main() {
  try {
    // 取得 api 失敗
    throw HttpCodeException(400, 'Bad Request');
  } on HttpCodeException catch (e) {
    // 針對特定 exception 處理
    print(e);
  } on Exception catch (e) {
    // 處理其他的 Exception
    print(e);
  } catch (e) {
    // 處理剩下非 Exception 的其他錯誤
    print(e);
  }
}

今日總結

今天的內容涵蓋很廣:
1. 類別在成為物件之前,需先定義 class 類別;物件是根據類別的定義創建的 instance
2. 創建 instance 是透過呼叫與 class 同名的 constructor 來產生
3. Named parameters 能具名的傳遞參數避免混淆
4. Named constructors 允許多個建構子存在,提高可讀性
5. Abstract class 僅用於定義類別而無法實例化。其他類別要引用時可以使用 implements 對其進行實作
6. Exception handling 則介紹了可使用 trycatchfinally 來處理 Exceptions,避免未預期的錯誤以及異常情況的發生

大家不妨使用 DartPad 好好的熟悉熟悉。明天將會是介紹 Dart 語法的最後篇章,我們將會提到同步/非同步處理,這在與網路進行交互的時候會不斷地使用,因此大家撐著點XD 。明天見囉~


上一篇
[Day 03] Dart 基礎語法 Part 2
下一篇
[Day 05] Dart 基礎語法 Part 4
系列文
Flutter 從零到實戰 - 30 天の學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言