---
# System prepended metadata

title: Dart 類的特性、建構函數、mixin 抽象
tags: [Dart, Flutter]

---

---
title: 'Dart 類的特性、建構函數、mixin 抽象'
disqus: kyleAlien
---

Dart 類的特性、建構函數、mixin 抽象
===

## Overview of Content

如有引用參考請詳註出處，感謝 :cat:

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入解析 Dart 語言：命名慣例、類特性、建構函數與抽象特性**](https://devtechascendancy.com/guide_dart-class_abstract-mixin-class/)
:::

:::info
以下使用 Dart SDK `3.4.3` 版本
:::

[TOC]


## Dart 檔案、方法取名慣例

先了解一下 Java、Dart 對於檔案取名方式的差異 (以下是取名慣例)

* Java 語言

    | 語言類型 | 檔案取名 | 方法取名 |
    | -------- | -------- | -------- |
    | Java | **開頭大寫** + 駝峰 | **開頭小寫** + 駝峰 |
    
    ```java=
    // Java 檔案名稱 -> HelloWorld.java         

    class HelloWorld {
        void sayHello() {

        }
    }
    ```
* Dart 語言

    | 語言類型 | 檔案取名 | 方法取名 |
    | -------- | -------- | -------- |
    | Dart | **開頭小寫** + 底線 | **開頭小寫** + 駝峰 |


    ```dart=
    // Dart 檔案名稱 -> hello_world.dart         

    class HelloWorld {
        void sayHello() {

        }
    }
    ```

## Dart 類特性

接下來我們再來了解 Dart 語言所創造的類的特性、特點（以下會跟 Java 語言做比較）

### 成員屬性域：訪問權

* Java 對於類中的屬性有詳細的描述 (`public`、`protected`、`private`、`package`)，而 Dart 並沒有這些描述字，**Dart 預設所有的成員皆是公開的**
    
    如果要表達「私有」的特性，**Dart 是透過底線（`_`）來將屬性、方法私有化**

    範例如下：
    
    1. **創建 `info_bean.dart` 檔案**：並在內部設置私有、公開成員
    
        ```dart=
        // info_bean.dart

        class InfoBean {
          String? name;
          int? age;

          // 變數前使用 `_` 就會變為私有變量
          int? _id, _phone;
        }
        ```
    
    2. **創建 `main.dart` 檔案**：

        ```dart=
        // main.dart

        import 'info_bean.dart';    // 引入自訂的類

        void main() {
          InfoBean infoBean = InfoBean();   // new 可以省略
          infoBean.name = "Alien";
          infoBean.age = 18;

          // 無法訪問 `_` 開頭的屬性
          infoBean.id = 9527;
          infoBean._phone = 886;
        }
        ```
        > ![image](https://hackmd.io/_uploads/HJd5mrjqR.png)

* Dart 這種添加底線的私有的特性是 **對於不同檔案才有用，無法使用在相同檔案中**

    :::warning
    也就是說私有方法、成員放在同個檔案中，就可以自由地被相同檔案中的程式存取
    :::
    
    範例如下：

    ```dart=
    class InfoBean {
      String? name;
      int? age;

      // 變數前使用 `_` 就會變為私有變量
      int? _id, _phone;
    }

    class InfoBeanReset {

      void reset(InfoBean bean) {
        bean.name = null;
        bean.age = null;

        // okay, 可以正常訪問
        bean._id = null;
        bean._phone = null;
      }

    }
    ```
    如下圖所見，我們可以看到就算使用底線描述的成員，由於在相同檔案中，仍可被其他的方法、類訪問
    > ![image](https://hackmd.io/_uploads/ryPGEriqC.png)



### Getter & Setter：屬性

* **屬性**（`Property`）是 Java 中有被提議但尚未實作的功能，**它鑑於成員變量與方法之間**… 使用屬性時，外部呼叫者看起來就是如同呼叫成員，而內部的實作則是方法

    ```mermaid
    graph LR

    subgraph class_內部
    屬性

    屬性 <--> |內部使用如同方法| 方法
    end

    外部訪問 --> |看起來就像成員| 屬性
    ```

    而在 Dart 中，每個成員都可以透過 `get`、`set` 關鍵字將成員轉為屬性使用

    | Dart 屬性操控 | 格式 | 注意 |
    | -------- | -------- | -------- |
    | get | <類型> get <屬性名稱> => 操作函數 | - |
    | set | <類型> set <屬性名稱> (<參數>) => 操作函數 | 不可以為 final 常量 |

    :::info
    * **當操作函數只有一行時可以使用 `=>` 描述函數體，當超過一行時就必須使用 `{}` 描述**
    
    * 另外在使用 `get` 屬性時，若參數為空也可以省略參數括號
    :::

* Dart 使用屬性的範例如下

    1. 類中普通的成員


        ```dart=
        class Point {
          // 一般的 member
          int x = 0;
          int y = 0;
        }
        ```
    
    2. 在類中宣告 get 屬性：在內部使用起來就如同使用方法一般，如果符合 Dart 語法規則就可以簡化為單行

        ```dart=
        class Point {
          ...

          // 宣告 get 屬性 xy
          int get xy {
            return x + y;
          }
          // 單行，省略大括號、return
          int get xy2 => x + y;
        }
        ```
    
    3. 在類中宣告 set、get 屬性：對於 Set 屬性則必須接收一個參數，該參數是外部使用者傳入的數值，通常會搭配一個私有數值保存真正的數值

        ```dart=
        import 'dart:math';
        
        class Point {
          ...

          // 用來保存真正的數值
          int _z = 0;
          
          // 宣告 set 屬性 z
          // void 可省略
          void set z(int value) => _z = value;

          // 定義 get 屬性 z
          int get z {
            return _z;
          }
          
          // 在這裡我們還針對使用者傳入的數值進行運作再儲存
          set zz(int value) => _z = pow(2, value).toInt();
        }
        ```

    * 使用範例：在外部使用者使用其來就如同使用類的成員一樣，不會感知到它是屬性的特性

        ```java=
        void main() {
          var point = Point()
            ..x = 10
            ..y = 200;

          // 呼叫 get 屬性（但不會感知到其實是屬性，用起來如同成員）
          print("point xy: ${point.xy}, z: ${point.z}");

          point.z = 200;
          print("point z: ${point.z}");

          point.zz = 3;
          print("point zz: ${point.z}");
        }
        ```
        
        > ![image](https://hackmd.io/_uploads/rkKfhSo5A.png)

* 適時的使用屬性可以增加程式可讀性與彈性，並減少複雜度並隱藏細節，第二個使用屬性的範例如下：


    ```java=
    class Rect {
      String des = "Rectangle";
      num left;
      num top;
      num width;
      num height;

      // constructor
      Rect(this.left, this.top, this.width, this.height);

      // 使用 get 定義了一個 right 屬性
      num get right => left + width;
      set right(num value)  => left = value - width;

      // 使用 get 定義了一個 description 屬性
      String get description => "I am $des";
      set description(String str) => des = str;
    }


    void main() {
      Rect rect = Rect(1,2,3,4);
      print(rect.right);    // 1+3
      rect.right = 5;
      print(rect.left);     // 5-3

      print(rect.description);
      rect.description = "Square";
      print(rect.description);
    }
    ```
    > ![image](https://hackmd.io/_uploads/H14nnHjcC.png)


### 操作符重載 operator 

* Dart 操作符重載，類似於 [**C++ 操作符多載**](https://hackmd.io/Roh5BF4MRN2YPgmZZ7c3Sw#operator-%E9%81%8B%E7%AE%97%E5%AD%90%E5%A4%9A%E8%BC%89) (而 Java 就不支持操作符重載)，同樣可以重新定義操作符號；透過 **++關鍵字 operator++** 就可以重載操作符，而且 Dart 比起 C++ 更靈活，**返回值不一定要是自身類**

    Dart 可重載的操作符如下表（全部的操作符請點擊 [**Operator 連結**](https://dart.dev/language/operators) 去官網訪站查看）

    | 符號 | 符號 | 符號 | 符號 |
    | - | - | - | - |
    | < | > | <= | >= |
    | - | + | * | / |
    | ~/ | % | [] | []= |
    | \| | & | << | >> |
    | ~ | == | % |  |

    ```dart=
    class Point {
      int _x, _y;

      // construct
      Point(this._x, this._y);

      Point operator + (Point point) {
        // 成員函數 可以直接使用
        return Point(point._x + _x, point._y + _y);
      }
      
      // 故意將返回值改為另外一個類
      String operator - (int i) {
        return "Hello";
      }

      int getX() {
        return _x;
      }

      int getY() {
        return _y;
      }
    }
    
    void main() {
        var p1 = Point(10, 10);
        var p2 = Point(20, 20);
        
        var p3 = p1 + p2;
        
        print("x value: ${p3.getX()}");
        print("y value: ${p3.getY()}");
        
        var strValue = p1 - 10;
        print("---> $strValue");
    }
    ```

    > ![image](https://hackmd.io/_uploads/HJqDBPs9C.png)

### Call 方法：重載呼叫符號

* Dart 有一個 call 函數，**它相當於小括號 `()` 符號的重載**（也就是呼叫符號重載）
    
    透過定義它我們只要使用 `()` 就可以省略省去呼叫 `call` 函數… 使用範例如下

    ```dart=
    class Closure {

      call(String str1, String str2) {
       print("$str1 ~~~ $str2");
      }

    }

    void main() {
      Closure c = Closure();

      // 可忽略呼叫 call 方法
      c("Use symbol of `()`", "Hello World");
      // 同上功能
      c.call("Use function to call", "Yo Man");
    }
    ```
    > ![image](https://hackmd.io/_uploads/B1cWvDoc0.png)


### Enum 類

* 如同 Java 的枚舉類，每個枚舉類型都有一個 index 的屬性可使用（用來標示元素的位置），並且可以透過 **`values`** 來取得 Enum 的集合

    Dart Enum 類使用的範例如下：

    ```dart=
    enum BaseColor {
      Red,
      Green,
      Blue
    }

    void main() {
      BaseColor baseColor = BaseColor.Blue;

      print("BaseColor index: ${baseColor.index} ");

      print("BaseColor toString: ${baseColor.toString()} ");

      List<BaseColor> values = BaseColor.values;
      print("BaseColor values: $values ");
    }
    ```
    > ![image](https://hackmd.io/_uploads/Byo6BPj50.png)

## 建構函數 constructor

Dart 的建構函數可以使用 [**可選命名參數**](https://devtechascendancy.com/dart-java-compare_exception_function-methods/#%E5%8F%AF%E9%81%B8%E5%91%BD%E5%90%8D%E5%8F%83%E6%95%B8%EF%BC%9A%E9%A0%90%E8%A8%AD%E5%8F%83%E6%95%B8)（`{}` 符號）、[**可選位置參數**](https://devtechascendancy.com/dart-java-compare_exception_function-methods/#%E5%8F%AF%E9%81%B8%E4%BD%8D%E7%BD%AE%E5%8F%83%E6%95%B8%EF%BC%9A%E9%A0%90%E8%A8%AD%E5%8F%83%E6%95%B8)（`[]` 符號）來包裹參數達到函數重載的特性 (**因為 Dart 沒有函數重載**)

### 命名建構函數：賦予建構函數意義

* **==命名建構函數==**：當要載入特定建構函數，可以使用指定的命名方式
    
    使用 「類名.屬性名」，**使用屬性名稱命名，可以更清晰的知道自己創建的建構函數使用到哪個屬性**，增加程式的可讀性 (以往建構函數並不具有可讀性，**因為它沒有名稱**，所以對於類的使用者來說缺乏語意)
    
    :::success
    * 有可選位置參數的話，為什麼還需要命名建構函數？

        同樣是為了更好的可讀性，它可以更好的去了解到目前使用的類有作用在哪，而不用每個建構參數都去了解！
    :::
    
    使用範例如下：

    * **定義命名建構函數**

        ```dart=
        class InfoBean {
          // 變數前使用 `_` 就會變為私有變量
          String? name;
          int? _id, age, _phone;

          InfoBean([this.name, this.age = 18, this._id, this._phone]);

          // 命函建構函數，命名為 name
          InfoBean.name(this.name);

          // 命函建構函數，命名為 nameAge
          InfoBean.nameAge(this.name, this.age);

          // 命函建構函數，命名為 a
          // 不必一定要使用屬性取名，但是使用屬性命名較清晰
          InfoBean.a(this.name);
          
          @override
          String toString() {
            return "name: $name, age: $age, id: $_id, phone: $_phone";
          }
          
        }
        ```

    * **使用命名建構函數**

        ```dart=
        import 'info_bean.dart';

        void main() {
          InfoBean infoBean = InfoBean();   // new 可以省略
          infoBean.name = "Alien";
          infoBean.age = 18;


          InfoBean kele = InfoBean.name("Kyle");
          print(kele);

          InfoBean pan = InfoBean.nameAge("Pan", 10);
          print(pan);

          // 假設使用不清晰的命名建構函數（缺乏語意）
          // 那與沒有命名相同，毫無含義
          InfoBean hello = InfoBean.a("Hello");
          print(hello);
        }
        ```
        > ![image](https://hackmd.io/_uploads/r1u1jDjq0.png)
    

### 初始化列表：與 C++ 比較

* **==初始化列表==**：這種初始化列表的使用方式如同 C++ 的建構函數一樣，使用冒號（`:`）開頭，後面接續需要賦值的屬性
    
    > 初始化列表很常被使用在定義私有的成員

    使用範例如下：

    * 在建構函數後使用初始化列表
    
        ```dart=
        class InfoBean {
          // 變數前使用 `_` 就會變為私有變量
          String name;
          int age;

          // 使用命名建構函數 加上 初始化列表
          InfoBean.initList(String name, int age)
              : this.name = name, this.age = age;    // 初始化列表在這


          @override
          String toString() {
            return "name: $_name, age: $_age";
          }
          
        }
        ```
    
    * 使用命名建構函數與初始化列表

        ```dart=
        void main() {
          InfoBean fish = InfoBean.initList("Fish", 19);
          
          print(fish);
        }
        ```
        > ![image](https://hackmd.io/_uploads/SkXmpvj9A.png)


        :::info
        * Dart 初始化列表與 C++ 初始化列表區別

            C++ 11 以後也有初始化列表，不過 C++ 的初始化列表不能使用在類別宣告 (除了 inline 函數)，因為 C++ 會把宣告與實現分開，而 Dart 不會

            ```cpp=
            #include <string>

            using std::string;

            class InfoBean {

            private:
                string mName;
                int mAge, mPhone;
                long mId;

            public:
                InfoBean(string name, 
                         int age = 18, 
                         long id, 
                         int phone)
                    : string(name), 
                      mAge(age), 
                      mId(id), 
                      mPhone(phone) {};	// 必須 inline 才可以在 '類宣告' 使用初始化列表

                virtual ~InfoBean();
            };
            ```
        :::

### 建構函數重定向、重定向的限制

* **重新定向建構函數**：java 在方法體內部使用 this 呼叫另一個建構函數，而 Dart 也可以使用 **初始化列表的方式呼叫別的建構函數** 
    
    * **建構函數重新定向**

        ```dart=
        class InfoBean {
          // 變數前使用 `_` 就會變為私有變量
          String? _name;
          int? _age;

          // 私有的命名函數
          InfoBean._nameAge(this._name, this._age) {
            print("call InfoBean.nameAge construct");
          }

          // 重新定位不可以有 function body
          InfoBean.redirect(String name, int age) : this._nameAge(name, age);    // 重新定向到 InfoBean._nameAge

          @override
          String toString() {
            return "name: $_name, age: $_age";
          }

        }
        ```

    * **呼叫重定向的建構函數**

        ```dart=
        void main() {
          final hey = InfoBean.redirect("Hey", 21);

          print(hey);
        }
        ```
        
        如下圖所見，呼叫重定向的建構函數後，它確被重新導向 `InfoBean._nameAge` 命名函數中
        
        > ![image](https://hackmd.io/_uploads/SkkOe_oqA.png)


        :::warning
        * **重新定向建構函數限制**

            1. 重新定向建構函數不可以使用 `this` 初始化

                > ![image](https://hackmd.io/_uploads/rkc4bdicR.png)

            2. 重新定向建構函數不能有方法體

                > ![image](https://hackmd.io/_uploads/HJ_v-Osc0.png)

        :::

### const 建構函數：加強效能

* **const 是在編譯期間就確定**，並且它也可以說用在建構函數上… 而需要實現這個功能就要一些條件（或是說限制） **^1.^ 使用 `const` 修飾建構函數，並 ^2.^ 聲明類的 ==++所有++ 成員為 `final`==，^3.^ 使用時必須以 `const` 宣告**

    :::info
    const 可以減少動態規劃記憶體空間的耗能，提高運行效能
    :::
    
    const 建構函數範例如下

    1. **所有的成員都要初始化**：
        
        以下是個錯誤示範，由於 `name` 成員沒有被定義，所以不能使用 `const` 宣告建構函數

        ```dart=
        // 錯誤示範
        
        class HelloWorld {
          final String name;

          const HelloWorld();
        }
        ```

        > ![image](https://hackmd.io/_uploads/Byq87dj9A.png)


    2. **所有的成員皆為 `final` 描述**：
        
        以下是個錯誤示範，雖然建構函數中都有賦予值，但是由於 `number` 成員沒有被 `final` 描述，所以也無法建構 `const` 建構函數
        
        ```dart=
        class HelloWorld {
          final String name;
          int number;

          const HelloWorld(this.name, this.number);
        }
        ```
        > ![image](https://hackmd.io/_uploads/HyRhm_i9C.png)

    
    3. 使用 `const` 建構函數：用法如同呼叫一般建構函數，但是要注意，若要發揮出 `const` 建構函數的較能，則呼叫時也要用 `const` 宣告

        ```dart=
        void main() {
          // 宣告時必須也是 const 開頭
          HelloWorld h1 = const HelloWorld("Apple", 2);
          HelloWorld h2 = const HelloWorld("Banana", 7);
          HelloWorld h3 = const HelloWorld("Apple", 2);
          
          // 沒有 const 描述如同 new，必定會運行時規畫出新的空間（較為耗效能）
          HelloWorld h4 = HelloWorld("Apple", 2);

          print("const h1 hashcode: ${h1.hashCode}");
          print("const h2 hashcode: ${h2.hashCode}");
          print("const h3 hashcode: ${h3.hashCode}");
          print("h4 hashcode: ${h4.hashCode}");

          print("h1 == h2: ${h1 == h2}");    // false
          print("h1 == h3: ${h1 == h3}");    // true
          print("h1 == h4: ${h1 == h4}");    // false
        }
        ```

        :::success
        * 如下圖，我們可以發現一件很有趣的事情
            
            使用 const 創建的物件，若是成員數值相同則會被歸類為同一個物件，規劃在同一塊記憶體上
        :::
        
        > ![image](https://hackmd.io/_uploads/r1X6Edi9A.png)



### 工廠建構函數：factory

* **Dart 有提供 ==`factory`== 關鍵字，讓自動產生一個 [工廠設計模式](https://devtechascendancy.com/object-oriented_design_factory_framework/) 的方法**（想了解更多工廠設計請點擊連結），而 `factory` 這種提供方式就像是替我們創建了一個靜態方法，而該方法專門產生類的實體（`instance`）
    
    > 但這個產生的實體並非是單例，而是新物件

    :::info
    * factory & static 方法差異

        factory 強制返回的就是該類的物件，而 static 則可以返回不同的物件 (或是不返回)
    :::
    
    `factory` 範例如下
    
    ```dart=
    // factory 示範
    class Manager {
      // 必須定義基礎 construct
      Manager();

      // 如果只有一行，你也可以簡化為 factory Manager.useFactory() => Manager();
      factory Manager.useFactory() {    // 不用宣告返回類型，自動定義返回類行為 Manager
        return Manager();
      }

      // 功能同上
      static Manager useStatic() {    // static 自己定義返回
        return Manager();
      }
    }

    void main() {
      Manager manager1 = Manager.useFactory();
      Manager manager2 = Manager.useFactory();

      print("Factory instance 1: ${manager1.hashCode}");
      print("Factory instance 2: ${manager2.hashCode}");
      print("Factory instance 1 equals 2: ${manager1 == manager2}");

    }
    ```
    > ![image](https://hackmd.io/_uploads/r18O9Oo9R.png)


### 使用 factory 實現 [單一工廠](https://devtechascendancy.com/object-oriented_design_factory_framework/#%E5%96%AE%E4%B8%80%E5%B7%A5%E5%BB%A0%EF%BC%9A%E9%9D%9C%E6%85%8B%E5%B7%A5%E5%BB%A0)、[覆用工廠](https://devtechascendancy.com/object-oriented_design_factory_framework/#%E6%8B%93%E5%B1%95%E5%B7%A5%E5%BB%A0%EF%BC%9A%E8%A6%86%E7%94%A8%E5%B7%A5%E5%BB%A0)

* 以下使用 Dart 的 `factory` 實現 [**單一工廠**](https://devtechascendancy.com/object-oriented_design_factory_framework/#%E5%96%AE%E4%B8%80%E5%B7%A5%E5%BB%A0%EF%BC%9A%E9%9D%9C%E6%85%8B%E5%B7%A5%E5%BB%A0) 模式
    
    實現單例的手法仍然相同，重點就是 ^1.^ 私有化建構函數、^2.^ 使用靜態變量保存單例物件、^3.^ 使用靜態方法（而這裡就改成使用 `factory` 關鍵字）往外提供單例物件

    * **實現物件單例**：

        ```dart=
        class Manager {
          static Manager? _instance;

          // Constructor
          // 使用 _<Function name> 可以讓外部函數無法調用
          Manager._internal();

          factory Manager.getInstance() {
            if(_instance == null) {
              _instance = new Manager._internal();
            }
            return _instance!;
          }
        }
        ```

    * **使用單例物件**

        ```dart=
        void main() {

          Manager m1 = Manager.getInstance();
          print("m1 instance: ${m1.hashCode}");

          Manager m2 = Manager.getInstance();
          print("m2 instance: ${m2.hashCode}");

          print("m1, m2 is same instance: ${m1 == m2}");

        }
        ```
        > ![image](https://hackmd.io/_uploads/BJ9GjOj9C.png)
        
        :::success
        想了解更多單例設計方式，請查看 [**Singleton 單例模式、設計 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/)
        :::
        
        :::warning
        **由於 Dart 是單執行緒，所以不用擔心同步問題** (不像 java 必須使用 `volatile`、`synchronized` 關鍵字)
        :::

* 同樣的，我們也可以使用 `factory` 關鍵字來實現 [覆用工廠](https://devtechascendancy.com/object-oriented_design_factory_framework/#%E6%8B%93%E5%B1%95%E5%B7%A5%E5%BB%A0%EF%BC%9A%E8%A6%86%E7%94%A8%E5%B7%A5%E5%BB%A0)
    
    其重點就是 ^1.^ 私有化建構函數、^2.^ 使用 Map 變量保存物件、^3.^ 使用靜態方法（而這裡就改成使用 `factory` 關鍵字）往外提供覆用物件

    * **實現覆用物件**：

        ```dart=
        class Login {
          final String name;

          static Map<String, Login> _map = {};

          Login._name(this.name);     // 私有建構函數

          factory Login.getCount(String name) {
            if(_map.containsKey(name)) {
              return _map[name]!;
            } else {
              Login i = Login._name(name);
              _map[name] = i;
              return i;
            }
          }

        }
        ```
        
    * **使用覆用物件**：

        ```dart=
        void main() {

          Login l1 = Login.getCount('name');
          print("l1 instance: ${l1.hashCode}");

          Login l2 = Login.getCount('name');
          print("l2 instance: ${l2.hashCode}");

          print("l1, l2 is same instance: ${l1 == l2}");

        }
        ```
        
        > ![image](https://hackmd.io/_uploads/Sk9C6_i5A.png)
    
        :::success
        想了解夠多的工廠設計方法，請查看 [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework)
        :::


## Dart 抽象設計

Java 的抽象設計有 `abstract class`、`interface` 並且它是單一繼承制度，而 Dart 在物件導向的設計上給予了不同於 Java 的設計（接下來這小節要介紹的）

### 抽象類 abstract class

* 使用 abstract 修飾符定義抽象類（`abstract class`）

    ```dart=
    abstract class Parent {
      // 抽象方法，省略 abstract
      void printInfo();
    }
    ```
    這個抽象類如同 Java 抽象類不能實例化

    > ![](https://i.imgur.com/I5N2KGw.png)


* 繼承抽象類如同 Java 使用關鍵字 `extends`，若是有抽象方法就必須實作，並且透過 super 呼叫父類建構函數、方法、成員

    範例如下 
    
    * **透過 `abstract` 關鍵字定義抽象類**

        ```dart=
        // 抽象類
        
        abstract class Parent {
          String name;

          Parent(this.name) {
            print('Parent construct');
          }
          // 抽象方法，省略 abstract
          void printInfo();

          void description() {
            name = "Mr.$name";
          }
        }
        ```
        
    * **定義子類，透過 `extends` 關鍵字繼承於父類**

        ```dart=
        // 子類
        
        class Child extends Parent {

          // 使用 super 呼叫父類
          Child(String name) : super(name) {
            print('Child construct');
          }

          @override
          void printInfo() {
            // 呼叫自身的 description 方法
            description();
            print('Hello World, $name');
            
            // 呼叫父類的 description 方法
            super.description();
            print('Call super function: Hello World, $name');
          }

          @override
          void description() {
            name = "Mrs.$name";
          }

        }
        ```

    * 使用抽象類範例：
    
        ```dart=
        void main() {
        //  Parent parent = Parent();   // 不能實體化抽象類

          Parent p1 = Child("Alien");   // 可多型
          p1.printInfo();
          print("p1.runtimeType: ${p1.runtimeType}");
        }
        ```
        > ![image](https://hackmd.io/_uploads/H1JAUEhq0.png)

:::danger
* **Dart 不支援內部類**，Java 才支援內部類

    ```dart=
    // 內部類
    class Hello {
        // Dart 不支援內部類
        class World {

        }
    }
    ```
    > ![image](https://hackmd.io/_uploads/B1pHvEn9C.png)

:::


### 介面 implements

* **Dart 並++沒有 `interface`++ 關鍵字**，**Dart 中每個類都隱式的定義了一個包含實例成員的介面(不管抽象 or 實體類)**
    
    :::warning
    * **那要怎麼分辨當前類是使用「繼承」的抽象類，還是使用「實作」的介面類**？
        
        這其實要依靠類的實現關鍵字來決定
        
        * 如果實現類要使用繼承類，那就使用 `extends` 關鍵字

        * 如果要使用實作介面類，那就要使用 `implements` 關鍵字

    :::
    
* 接下來我們看實現類如何透過實作，實體類（`class`）的介面、抽象類（`abstract class`）的介面
    
    * **實作「實體類」的介面**
        
        * 定義實體類
            
            由於實體類的方法必須定義方法體，所以以下使用方法的空實現
            ```dart=
            class IInterface {
            
              // 空實現
              void connect() {}    // 實體類，所以需要 "{}"
              
              void disconnect() {}
              
            }
            ```
        
        * 實現類把實體類作為介面使用
            
            使用 `implements` 關鍵字實作實體類的介面方法… 由於 `IInterface` 的方法中有方法體，所以這邊的實現算是覆寫（`override`）

            ```dart=
            class MyClass implements IInterface {

              @override
              void connect() {
                print("connect");
              }

              @override
              void disconnect() {
                print("disconnect");
              }

            }
            ```

    * **實作「抽象類」的介面**

        * 定義抽象類
            
            由於使用抽象類，所以抽象方法不需要方法體
            ```dart=
            abstract class AClass {
            
              // 定義抽象方法
              void group();
              
              void item();
              
            }
            ```
        
        * 實現類把抽象類作為介面使用
            
            同樣使用 `implements` 關鍵字來實作抽象類的方法
            ```dart=
            class Router implements AClass {
              @override
              void group() {
                print("group");
              }

              @override
              void item() {
                print("item");
              }

            }
            ```
            
 


### 混合類 mixin：類多重繼承/菱形問題

* Dart 與一般的高級語言（像是 `Java`、`Kotlin`、`Swift`）不同的特點之一就是「**多重繼承**」（但 Dart 其實並非真正實現多重繼承，而是使用 Mixins 手動來達成「類似多重繼承」的效果）

    Dart 以 `mixin` 關鍵字來定義「**混合類**」，並以 `with` 關鍵字來使用混合類

    混合類範例如下

    ```dart=
    // 使用 mixin 關鍵字定義混合類
    mixin class Hostel {

      String describe() {
        return "Hostel";
      }

    }
    
    // 配合 `with` 關鍵字就可以使用混合類
    class Voyage with Hostel {

    }
    ```
    
    :::info
    * 當然 `mixin` 關鍵字不只可以使用在類上，以可以在另一个 mixin 中使用 mixin

        ```dart=
        mixin Logger {
          void log(String message) {
            print('Log: $message');
          }
        }

        mixin Tracker {
          void track(String event) {
            print('Tracking: $event');
          }
        }

        mixin Analytics on Logger, Tracker {
          void analyze() {
            log('Analyzing data');
            track('Data analyzed');
          }
        }
        ```
    :::
    
* 在 Dart 中，`mixin` 是一組可以應用到其他類上的功能，而 `mixin class` 它具有一定的限制，**這些限制幫助避免多重繼承的菱形問題**（最後會說明菱形問題）；

    mixin 類的限制如下所示

    * **混合類除了空建構函數之外，不能有有參建構函數**

        ```dart=
        mixin class Hostel {
          final String address;

          Hostel(this.address);

          String describe() {
            return "Hostel";
          }

        }
        ```

        > ![image](https://hackmd.io/_uploads/BkuJlr2cR.png)

    * **混合類除了 Object 類以外，不能在繼承其它類**，利用這個規則來避免多重繼承的菱形問題！

        ```dart=
        class Coffee {

          String describe() {
            return "Coffee";
          }


        }

        mixin class Hostel extends Coffee {    // 混合類不能再繼承其他類！

          String describe() {
            return "Hostel";
          }

        }
        ```

        > ![image](https://hackmd.io/_uploads/B1DXyS39C.png)

        :::warning
        * **什麼是菱形問題？混合類（`mixin class`）有多重繼承的菱形問題**？

            在多重繼承裡有個較為麻煩的「**菱形問題**」：菱形問題是說，假設繼承兩個混合類，而這兩個混合類中又有相同的方法，那呼叫時到底該定位到哪個實現呢？

            傳統多重繼承導致的菱形問題概念圖如下（下圖問題不會出現在 Dart 中）

            ```mermaid
            classDiagram
                class abstractA {
                    +method()
                }
                class abstractB {
                    +method()
                }
                class abstractC {
                    +method()
                }
                class D {
                    +method()
                }
                abstractA <|-- abstractB
                abstractA <|-- abstractC
                abstractB <|-- D
                abstractC <|-- D
            ```

            而 Dart 透過限制混合類（`mixin class`）的繼承來抑制這種菱形問題
        :::


### 混合類 mixin：方法解析順序 MRO

* 如上面小節所述，Dart 可以實現類似多重繼承的功能，並且雖然透過一些限制來避免了菱形問題，但還有另一個問題是「Dart 如何定位 ` mixin class` 的方法」
    
    如下圖所示，D 類別繼承了 B、C 類，但雙方都實現了同名的方法，那在呼叫方法 `method` 時該定位到哪個方法呢？

    ```mermaid
    classDiagram
        class mixinB {
            +method()
        }
        class mixinC {
            +method()
        }
        class D {
            +method()
        }
        mixinB <|-- D
        mixinC <|-- D
    ```
    
* 對於這個問題，Dart 使用了 **方法解析順序（`Method Resolution Order`, MRO）** 來確保在使用 `mixin` 和 `mixin class` 時，方法的調用順序是可預測的和一致的，從而解決了多重繼承定位方法的問題
    
    使用範例（測試）如下
    
    * 定義多個 `mixin class`，並類這些混合類中都擁有相同的方法

        ```dart=
        mixin class Coffee {
            
          // 相同的 describe 方法
          String describe() {
            return "Coffee";
          }

        }

        mixin class Bread {

          // 相同的 describe 方法
          String describe() {
            return "Bread";
          }

        }

        mixin class Hostel {

          // 相同的 describe 方法
          String describe() {
            return "Hostel";
          }

        }
        ```
    
    * 定義兩個實現類，這個個實現類都繼承多個混合類，並呼叫混合類中相同的方法（`describe()`），不過 **繼承的順序不同！**

        ```dart=
        class VoyageFinalHostel with Coffee, Bread, Hostel {

          void printMsg() {
            // 測試同名方法，Hostel 擺最後
            print("Final mixin method of describe: ${describe()}");    
          }

        }

        class VoyageFinalCoffee with Hostel, Bread, Coffee {

          void printMsg() {
            // 測試同名方法，Coffee 擺最後
            print("Final mixin method of describe: ${describe()}");    
          }

        }
        ```
        
    * 測試多重混合繼承後，對於相同的方法會被定位到哪類上

        ```dart=
        void main() {
          print("Use VoyageFinalHostel");
          VoyageFinalHostel().printMsg();

          print("\nUse VoyageFinalCoffee");
          VoyageFinalCoffee().printMsg();
        }
        ```
        
        如下圖所見，我們可以看到混合方法會依照順序被覆蓋，由於 `VoyageFinalHostel` 類最後繼承的混合類是 `Hostel`，所以 `describe()` 就會定位到 Hostel；而 `VoyageFinalCoffee` 類最後的繼承混合類是 `Coffee`，所以會定位到 Coffee
        
        > ![image](https://hackmd.io/_uploads/SJeeYH3qR.png)

## Appendix & FAQ

:::info
:::

###### tags: `Flutter`、`Dart`
