Skip to content

2.定義類與使用類

Dart是一門基於類和繼承的面向對象語言,講到對象,我們就不得不談類。對像是類的實例,對像也是由類構造出來的,更通俗地講,我們可以把類理解為對象的模板,在類中定義了對象的屬性和方法。

自定義類與構造方法

我們之前在Dart中使用的任何數據類型其實都是類,包括描述整數的int類、描述浮點數的double類、描述字符串的string類等。在開發中,我們可以根據實際需要定義自己的類,在Dart中,使用class關鍵字進行類的定義,示例如下:

class Circle{
    double radius;
    double centerX;
    double centerY;
}

上面我們簡單定義了一個圓形類,其中定義了3個屬性,分別描述圓的半徑和圓心的X、Y坐標。儘管我們沒有給這個圓形類定義任何方法,但它已經是一個完整的自定義類了,我們可以通過它構造圓形對象來存儲數據。

通過類創建對象需要調用類的構造方法,類的構造方法通常與類名一致,也可以定義獨立名稱的構造方法,但是依然需要通過類名來調用。當我們定義完一個類時,Dart默認會生成一個沒有參數的構造方法,我們可以直接通過類名進行調用,例如:

main() {
    var circle = new Circle();//使用參數構造圓形對象
    circle.radius = 3;
    circle.centerX = 1;
    circle.centerY = 1;
}

class Circle{
    double radius;
    double centerX;
    double centerY;
}

類實例中封裝的屬性可以通過點語法來進行訪問,可以進行屬性值的設置,也可以進行屬性值的讀取。在默認情況下,如果沒有對實例的某個屬性進行過賦值,此屬性的值就為null。但是,在進行類的定義時,也可以為屬性提供默認值,例如:
class Circle{
    double radius = 0;
    double centerX = 0;
    double centerY = 0;
}

構造方法是類的實例對像生成過程中的重中之重。Dart默認提供的構造方法雖然可以完整地構造出對象,但是其中定義的屬性都為null值或默認值。我們也可以通過實現構造方法來干預對像生成的過程,例如:
class Circle{
    double radius = 0;
    double centerX = 0;
    double centerY = 0;
    Circle(double radius, double centerX, double centerY){
        this.radius = radius;
        this.centerX = centerX;
        this.centerY = centerY;
    }
}

如上所示,重寫的構造方法中定義了3個參數,分別對應圓形的半徑和圓心點X、Y坐標。在使用Circle類構造對象時,需要將指定的參數傳入,例如:
var circle = new Circle(6, 1, 1);

有一點需要額外注意,一旦重寫了構造方法,默認的無參構造方法將不再可用。在上面的構造方法中,this關鍵字指的就是當前實例對象,構造方法的實質是將對象屬性的賦值過程由外界封裝到類的內部。

在Dart中,關於構造方法的編寫還有一個小技巧,一般情況下,我們可以直接將構造方法定義成如下模樣,Dart會自動進行參數和屬性的匹配,進行賦值,非常方便。

main() {
    var circle = new Circle(6,1,1);//使用參數構造圓形對象
    print(circle.radius);//6.0
}
class Circle{
    double radius = 0;
    double centerX = 0;
    double centerY = 0;
    Circle(this.radius, this.centerX, this.centerY)
}

有時,一個類需要有多個構造方法,比如自定義的圓形類,很多時候需要快速創建出單位圓(圓心為坐標原點、半徑為1的圓)。這時,就可以定義一個便捷的構造方法幫助我們直接生成單位圓,這類構造方法也被稱為命名構造方法,示例如下:
main() {
    var circle2 = Circle.standard();//使用參數構造圓形對象
    print(circle2.radius);//1.
}
class Circle{
    double radius = 0;
    double centerX = 0;
    double centerY = 0;
    Circle(this.radius, this.centerX, this.centerY)
    Circle.standard(){
        this.radius = 1;
        this.centerX = 0;
        this.centerY = 0;
    }
}

命名構造方法通常用來快速地創建標準對象,同樣,命名構造方法也可以有參數,並且只要參數名與類中定義的屬性名一致,也可以使用Dart自動匹配賦值的特性。

實例

類封裝了屬性和方法,屬性用來存儲描述類的數據,方法用來描述類的行為。在面向對象編程中,生活中的事物都可以模擬成程序中的對象,例如一個教務系統軟件中一定有教師相關信息,每一位教師都是一個教師對象,可以創建教師類來描述教師對象,示例代碼如下:

class Teacher{
    String name;
    int number;
    String subject;
    Teacher(this.name, this.number, this.subject);
    void sayHi(String toName){
        print("Hello ${toName},我是${this.name}老師!ID號碼為${this.number}");
    }
    void teaching(){
        print("${this.name}老師正在進行${this.subject}教學。");
    }
}
main(){
  var teacher = Teacher("Ahwu", 1111, "APP應用設計");
  teacher.sayHi("阿明子");
  teacher.teaching();
}

Output:
Hello 阿明子,我是Ahwu老師!ID號碼為1111

Ahwu老師正在進行APP應用設計教學。

類中還有兩個非常特殊的方法:Setters方法與Getters方法Setters方法用來設置對象屬性Getters方法用來獲取對象屬性。其實當我們使用點語法訪問對象屬性信息時,調用的就是Setters方法或Getters方法,在定義屬性時,Dart會自動生成默認的Setters方法和Getters方法。Setters方法和Getters方法的另一大作用是定義附加屬性,附加屬性也可以理解為計算屬性,即這些數據通常不是描述對象的最原始數據,而是通過計算得來的,例如: ^6c62bb

main() {
    var teacher = Teacher("Ahwu",1111,"APP應用設計");
    teacher.sayHi("阿明子");//Hello 阿明子,我是Ahwu老師!ID號碼為1111
    teacher.teaching();//Ahwu老師正在進行APP應用設計教學。
    print(teacher.description);//Ahwu:APP應用設計
    teacher.description = "Lucy:JavaScript";
    teacher.teaching();//Lucy老師正在進行JavaScript教學。
}

class Teacher {
    String name;
    int number;
    String subject;
    Teacher(this.name, this.number, this.subject);
    void sayHi(String toName){
        print("Hello ${toName},我是${this.name}老師!ID號碼為${this.number}");
    }
    void teaching(){
        print("${this.name}老師正在進行${this.subject}教學。");
    }
    String get description{
        return "${this.name}:${this.subject}";
    }
    set description(String value){
        List a = value.split(":") as List;
        print(a);//會把value根據":"做切割
        this.name = (value.split(":") as List)[0];
        this.subject = (value.split(":") as List)[1];
    }
}

上面的代碼中,description就是附加屬性,其並沒有真正佔用內存空間進行存儲,而是通過其他屬性計算而來的。

抽象類與抽象方法

抽像類是在開發中較為難理解的一點。在Dart中,抽像類中可以定義抽象方法。所謂抽象方法,是指只有定義卻沒有實現的方法,抽像是指接口開發的基礎。
以生活中汽車產品的生產為例,一輛完整的汽車的生產往往需要多個廠家合作,例如發動機生產廠家、輪胎生產廠家、門窗內設生產廠家等。不同的廠家生產的配件若要完美地組合成一輛汽車,則必須遵守統一的標準,也可以理解為按照實現的協議進行生產。在編程中也是這樣的,一個複雜的程序可能需要很多開發者甚至多個部門進行配合開發,每個開發者或部門負責一個模塊,而模塊之間又可以進行交互與連通,這時==在程序真正編寫前,我們就需要先約定協議、制定接口。==

abstract class TeacherInterface{
    void teaching();
}
class Teacher implements TeacherInterface{
    String name;
    int number;
    String subject;
    Teacher (this.name, this.number,this.subject) ;
    void sayHi(String toName){
        print("Hello ${toName},我是${this.name}ID為${this.number}");
    }
    void teaching(){
        print("${this.name}老師正在進行${this.subject}教學。");
    }
}

上面的TeacherInterface接口中只定義了一個抽象方法,Teacher類可以對這個接口進行實現,示例代碼如下:
abstract class TeacherInterface {
    void teaching();
}

abstract class PeopleInterface{
    void sayHi(String name);
}
class Teacher implements TeacherInterface, PeopleInterface{
    String name;
    int number;
    String subject;
    Teacher (this.name, this.number,this.subject) ;
    void sayHi(String toName){
        print("Hello ${toName},我是${this.name}ID為${this.number}");
    }
    void teaching(){
        print("${this.name}老師正在進行${this.subject}教學。");
    }
}

抽象類無法被直接用來創建實例對象。抽象類只是一個定義了一組方法或屬性的模板,它描述了一個類型的通用行為。 抽象類通常包含一些抽象方法,這些方法在抽象類中聲明但沒有實際實現。

==當你想要使用一個抽象類定義的方法和行為時,你需要創建一個實現了這個抽象類接口的具體類。==這個具體的類必須提供抽象類中所有抽象方法的實際實現。這樣,你才能通過這個具體類的實例來使用抽象類定義的功能。

換句話說,抽象類僅僅是一個規範或藍圖,它告訴你應該實現哪些方法,但它本身不具備實際的功能,因此無法直接用來創建對象。要使用抽象類定義的功能,你需要建立一個實現了這個抽象類的具體類的對象。這個具體類提供了抽象方法的實際代碼實現,從而讓你能夠實際使用這些功能。

類的繼承

繼承是類的重要特性。子類繼承父類後,可以直接使用父類中定義的屬性和方法,並且子類可以對父類的方法進行重寫以實現定制化的功能。 繼承其實很容易理解,現實中的事物為了方便描述與歸納,也會進行分門別類,例如生物界可以分為動物和植物,動物類下面又可以分出魚類、鳥類等,動物類就是生物的子類,魚類、鳥類又是動物類的子類。越是上層的類,封裝的屬性和方法越通用,子類會在父類的基礎上進行擴展,增加許多獨特的屬性和方法。

main() {
    var teacher = Teacher("Ahwu", 26);
    teacher.sayHi();
    teacher.teaching();
}

class People{
  String name;
  int age;
  People(this.name, this.age);
  void sayHi(){
    print("Hello");
  }
}

class Teacher extends People{
    Teacher(name, age):super(name, age);//調用父類的構造方法
  void teaching(){
    print("${this.name}正在教學");
  }
}

Output:
Hello

Ahwu正在教學

Warning

如上面的代碼所示,Teacher類直接繼承了People類的姓名、年齡屬性和sayHi方法。但是需要注意,構造方法是不會被繼承的,在Teacher類中可以使用super關鍵字來調用父類的方法,包括構造方法。

子類也可以重載父類的方法,並且在重載時可以調用對應的父類方法,例如:

main() {
    var teacher = Teacher("Ahwu", 26);
    teacher.sayHi();
    teacher.teaching();
}

class People{
  String name;
  int age;
  People(this.name, this.age);
  void sayHi(){
    print("Hello");
  }
}

class Teacher extends People{
    Teacher(name, age):super(name, age);//調用父類的構造方法
  void teaching(){
    print("${this.name}正在教學");
  }
  @override
  void sayHi(){
    super.sayHi();
    print("我是${this.name}");
  }
}

Output:
Hello

我是Ahwu

Ahwu正在教學

可以看到在上述的程式碼將sayHi()這個函數重新編寫,並覆蓋過去,因此當在調用Teacher這個class時,他的sayHi()函數會是print("我是${this.name}");

運算符重載

我們前面在學習運算符相關內容時了解到,Dart中的運算符非常靈活,例如加法運算符除了可以用在數值的加法運算外,在字符串對象間使用也可以實現字符串的拼接功能。 Dart中的運算符之所以如此靈活,是由於Dart是一門完全面向對象的語言,而運算符的運算實質是方法的調用。因此,我們也可以為自定義的類添加運算符方法,例如:

main(){
  var size1 = Size(3, 6);
  var size2 = Size(2, 2);
  var size3 = size1 + size2;
  size3.desc();
}

class Size{
  num width;
  num height;
  Size(this.width, this.height);
  Size operator +(Size size){
    return Size(this.width+size.width, this.height+size.height);
  }
  desc(){
    print("width:${this.width},height:${this.height}");
  }
}

當你註解掉 Size operator +(Size size) 方法時,程式碼將出錯,原因是在 size1size2 進行 + 運算時,Dart 不知道如何處理這個操作,因為它找不到適當的運算符重載方法(operator overloading)。這將導致以下錯誤:

The operator '+' isn't defined for the type 'Size'.

在Dart中,當你使用 + 運算符對自定義類型的對象進行操作時,你需要為該類型定義相應的 + 運算符重載方法。在你的程式碼中,Size operator +(Size size) 方法==實現了這個運算符的重載,它告訴Dart如何將兩個 Size 對象相加。==

如果你註解掉這個方法,Dart 將不知道如何處理 size1 + size2 運算,因此會報錯。為了使運算符 + 正確運作,你需要保留 Size operator +(Size size) 方法的實現,這樣Dart就能夠使用它來執行 size1 + size2 運算。這個方法返回一個新的 Size 對象,包含了兩個 Size 對象的寬度和高度之和。

正確輸出:
width:5,height:8

noSuchMethod方法

對於非抽像類,當定義了一個沒有實現的方法時,代碼的運行會產生異常,例如:

main(){
  var people = People();
}

class People{
  void sayHi();
}

上述程式碼會拋出異常訊息,因為如果一個類實現了某個接口或者繼承了某個抽像類,卻沒有全部實現接口和抽像類中聲明的方法,就會產生如上的異常。
實際開發中,接口或抽像類中的方法有時並不需要全部實現,這時可以選擇重載noSuchMethod方法,如果重載了這個方法,當對象調用到這些未實現的方法時,就會執行noSuchMethod方法,例如:
main() {
    var teacher = Teacher();
    teacher.sayHi();//调用了未实现的方法Symbol("sayHi")
}


abstract class People{
    void sayHi();

}

class Teacher extends People {
    @override
    void noSuchMethod(Invocation invocation) {
        print('調用了未實現的方法' +
        '${invocation.memberName}');
    }
}

Output:
調用了未實現的方法Symbol("sayHi")

需要注意,雖然重載noSuchMethod方法可以避免調用未定義方法異常的產生,但是其也會掩蓋代碼邏輯中的錯誤,在實際開發時,要盡量少使用這種方式。

枚舉類型

枚舉是一種特殊的類型,其用來描述有限個數的數據集合。比如前面在定義教師類時,其中定義了一個科目的屬性,雖然我們將其定義為字符串類型,但是這並不十分嚴謹,教師所教學科目的類目是有限的,而且應該是固定的,不會隨意增減,對於這種情況,使用枚舉非常合適。

main(){
  var teacher = Teacher("Ahwu", Subject.Dart);
  teacher.desc();
}

enum Subject{
  Dart,
  JavaScript,
  Python,
  Flutter
}

class Teacher{
  String name;
  Subject subject;
  Teacher(this.name, this.subject);
  desc(){
    print("${this.name}:${this.subject}");
  }
}

Output:
Ahwu:Subject.Dart

enum關鍵字用來定於枚舉,其實枚舉與數組類似,其中的數據也都有下標,從0開始,例如:
main(){
  var teacher = Teacher("Ahwu", Subject.JavaScript);
  teacher.desc();
  print(teacher.subject.index);
}

Output:
Ahwu:Subject.JavaScript

1

使用print(Subject.values); 可以印出所有在Subject下的數據
Output:
[Subject.Dart, Subject.JavaScript, Subject.Python, Subject.Flutter]

可使用switch來選擇輸出
main(){
  var teacher = Teacher("Ahwu", Subject.JavaScript);
  switch(teacher.subject){
    case Subject.Dart:
      print("Dart老師");
    case Subject.JavaScript:
      print("JavaScript老師");
    case Subject.Python:
      print("Python老師");
    case Subject.Flutter:
      print("Flutter老師");   
  }
//   teacher.desc();
//   print(teacher.subject.index);
//   print(Subject.values);
}

enum Subject{
  Dart,
  JavaScript,
  Python,
  Flutter
}

class Teacher{
  String name;
  Subject subject;
  Teacher(this.name, this.subject);
  desc(){
    print("${this.name}:${this.subject}");
  }
}

Mixin特性

Mixin是Dart中非常強大的一個特性。通過前面的學習,我們知道,Dart只支持單繼承,即子類只能夠有一個父類。有的時候,我們需要集合多個類的功能來實現一個複雜的自定義類,就需要使用到Mixin特性。
Mixin從字面來理解為混合的意思,顧名思義,Mixin的主要作用也是進行混合,其允許一個類將其他類的功能混合進來,例如:

main() {
  var bird = Bird("鳥類");
  bird.desc();
}

class Animal {
  String name;
  Animal(this.name);
}

mixin Descript {
  desc() {
    print(this);
  }
}

class Bird extends Animal with Descript {
  Bird(name) : super(name);

  @override
  desc() {
    super.desc();
    print("這是一隻${this.name}。");
  }
}

使用mixin定義的Mixin類不能夠被繼承,也不能夠進行實例化。Mixin類本身可以進行繼承,如果使用class關鍵字進行定義,就和普通類的集成語法一致,如果使用mixin關鍵字進行定義,就使用on關鍵字進行繼承,例如:

mixin Sleep on Object{
  sleep(){
    print("Sleeping");
  }
}

這段程式碼是 Dart 語言中的 mixin 定義。讓我們來解釋它的含義:

  1. mixin Sleep: 這是一個 Dart mixin 的定義,名稱為 Sleep。Mixin 是一種 Dart 的特性,它允許你將一個類別的功能(方法和屬性)混入(或組合)到其他類別中,以實現代碼的重用。

  2. on Object: 這個 mixin 被應用在 Object 類別上。Object 是 Dart 中所有類別的超類別,因此這個 mixin 可以被應用到任何 Dart 物件(類別的實例)上。

  3. sleep(): 這是 mixin 中定義的一個方法。當這個 mixin 被應用到某個類別上時,這個類別就會繼承 sleep 方法。該方法的作用是列印一條訊息,顯示 “Sleeping”,表示該物件正在進行睡眠操作。

簡而言之,這個 mixin 定義了一個 sleep 方法,並可以將它混入到其他 Dart 類別中,以便這些類別也可以使用 sleep 方法來執行睡眠操作。這樣的 mixin 可以幫助實現代碼的重用和組合。

Note

在程式設計中,”實例” 指的是類別的一個特定物件或示例,它是根據該類別的定義所創建的。實例具有類別定義的屬性和方法,並且可以存儲特定數據並執行操作。

以下是一些示例說明:

  1. 類別和實例:假設你有一個類別叫做 “汽車”,該類別描述了汽車的屬性(例如品牌、型號、顏色)和方法(例如啟動、停止、加速)。當你創建一輛具體的汽車,例如一輛 Toyota Camry,這輛汽車就是 “汽車” 類別的一個實例。這個實例具有特定的品牌、型號和顏色,並且可以執行汽車類別中定義的方法。

  2. 物件導向程式設計:實例是物件導向程式設計(Object-Oriented Programming,縮寫為 OOP)中的基本概念之一。在 OOP 中,類別是用來定義物件的模板,而實例則是根據這個模板創建的具體物件。你可以創建多個不同的實例,它們都基於同一個類別,但具有不同的狀態和行為。

  3. 程式語言中的實例:在不同的程式語言中,實例可能有不同的名稱。例如,在 Java 中,實例通常被稱為 “物件”(Object),在 Python 中,它們也被稱為 “物件”,而在 JavaScript 中,它們被稱為 “實例”(Instance)。

總之,”實例” 是一個特定類別的物件,它擁有該類別定義的屬性和方法,並代表一個特定的數據實體。物件導向程式設計中,實例是程式的基本構建塊之一,用於建模現實世界的事物和處理程式中的數據和操作。

main() {
    var obj = Sub();
    obj.func();
}


class Father extends Mixin {
    func(){
        print("father func");
    }
}

class Mixin {
    func(){
        print("Mixin fucn");
    }
}


mixin One on Mixin {
    func(){
        print("one func");
    }
}

mixin Two {
    func(){
        print("two func");
    }
}



class Sub extends Father with One,Two {
    func(){
        print("sub func");
    }
}

上面的代碼使用到了繼承和多混合,並且子類、父類、Mixin類中都對相同的方法進行了實現,運行代碼,控制台會打印出“sub func”。可以看出,無論是Mixin還是繼承,子類中的方法實現優先級都是最高的,將子類實現的方法去掉,再次運行,控制台將輸出“two func”,這說明Mixin中方法的優先級要高於父類中方法的優先級,並且在多混合中,後混合的優先級更高。因此,在對於繼承和混合一起使用的複雜場景中,你需要牢記如下兩個原則:

  1. 當前類中方法的優先級最高。
  2. Mixin中方法的優先級高於繼承父類方法的優先級,並且在混合時,Mixin從左到右優先級依次增高。

前面我們定義類時定義的屬性和方法都是針對實例的,即由類的實例對象進行訪問或調用。其實,類本身也是一種對象,在類中也可以定義類屬性與類方法,使用類名直接進行訪問和調用。示例代碼如下:

main(){
  print(Animal.name);
  Animal.printName();
}

class Animal{
  static String name = "動物類";
  static printName(){
    print(name);
  }
}

類屬性也被稱為類靜態屬性,其通常用來存放某些固定的且在類的所有實例中共享的屬性,類方法也被稱為類靜態方法,通常會提供一些靜態的計算功能。


Last update : 13 novembre 2024
Created : 13 novembre 2024

Comments

Comments