Overview of Content
在這篇文章中,我們將全面探索 Dart 語言的核心,有關於「類」的基礎概念;本文涵蓋了 Dart 檔案與方法的命名慣例,深入剖析類的特性,包括成員屬性域的訪問權、屬性 Getter & Setter 的使用、操作符重載、以及 Enum 類等進階功能
此外,文章詳細介紹了 Dart 的建構函數,包括命名建構函數的意義、初始化列表的應用、建構函數重定向及其限制,以及如何使用 const
和 factory
建構函數來提升效能與靈活性。我們也會比較 Dart 與 C++ 在初始化列表上的異同(也會適時的與 Java 語言做比較)
最後,對於面向對象設計中的抽象設計部分,我們將討論 Dart 的抽象類、介面、以及混合類 mixin
的使用,並深入解析多重繼承和菱形問題的解決方案,幫助你掌握 Dart 的方法解析順序(MRO)特性
以下使用 Dart SDK
3.4.3
版本
寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀
個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看
Dart 檔案、方法取名慣例
先了解一下 Java、Dart 對於檔案取名方式的差異 (以下是取名慣例)
● Java 語言
語言類型 | 檔案取名 | 方法取名 |
---|---|---|
Java | 開頭大寫 + 駝峰 | 開頭小寫 + 駝峰 |
// Java 檔案名稱 -> HelloWorld.java
class HelloWorld {
void sayHello() {
}
}
● Dart 語言
語言類型 | 檔案取名 | 方法取名 |
---|---|---|
Dart | 開頭小寫 + 底線 | 開頭小寫 + 駝峰 |
// Dart 檔案名稱 -> hello_world.dart
class HelloWorld {
void sayHello() {
}
}
Dart 類特性
接下來我們再來了解 Dart 語言所創造的類的特性、特點(以下會跟 Java 語言做比較)
成員屬性域:訪問權
● Java 對於類中的屬性有詳細的描述 (public
、protected
、private
、package
),而 Dart 並沒有這些描述字,Dart 預設所有的成員皆是公開的
如果要表達「私有」的特性,Dart 是透過底線(_
)來將屬性、方法私有化
範例如下:
A. 創建 info_bean.dart
檔案:並在內部設置私有、公開成員
// info_bean.dart
class InfoBean {
String? name;
int? age;
// 變數前使用 `_` 就會變為私有變量
int? _id, _phone;
}
B. 創建 main.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;
}
● 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;
}
}
如下圖所見,我們可以看到就算使用底線描述的成員,由於在相同檔案中,仍可被其他的方法、類訪問
Getter & Setter:屬性
● 屬性(Property
)是 Java 中有被提議但尚未實作的功能,它鑑於成員變量與方法之間… 使用屬性時,外部呼叫者看起來就是如同呼叫成員,而內部的實作則是方法
而在 Dart 中,每個成員都可以透過 get
、set
關鍵字將成員轉為屬性使用
Dart 屬性操控 | 格式 | 注意 |
---|---|---|
get | <類型> get <屬性名稱> => 操作函數 | - |
set | <類型> set <屬性名稱> (<參數>) => 操作函數 | 不可以為 final 常量 |
● 當操作函數只有一行時可以使用
=>
描述函數體,當超過一行時就必須使用{}
描述● 另外在使用
get
屬性時,若參數為空也可以省略參數括號
● Dart 使用屬性的範例如下
A. 類中普通的成員
class Point {
// 一般的 member
int x = 0;
int y = 0;
}
B. 在類中宣告 get 屬性:在內部使用起來就如同使用方法一般,如果符合 Dart 語法規則就可以簡化為單行
class Point {
...
// 宣告 get 屬性 xy
int get xy {
return x + y;
}
// 單行,省略大括號、return
int get xy2 => x + y;
}
C. 在類中宣告 set、get 屬性:對於 Set 屬性則必須接收一個參數,該參數是外部使用者傳入的數值,通常會搭配一個私有數值保存真正的數值
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();
}
● 使用範例:在外部使用者使用其來就如同使用類的成員一樣,不會感知到它是屬性的特性
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}");
}
● 適時的使用屬性可以增加程式可讀性與彈性,並減少複雜度並隱藏細節,第二個使用屬性的範例如下:
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);
}
操作符重載 operator
● Dart 操作符重載,類似於 C++ 操作符多載 (而 Java 就不支持操作符重載),同樣可以重新定義操作符號;透過 關鍵字 operator 就可以重載操作符,而且 Dart 比起 C++ 更靈活,返回值不一定要是自身類
Dart 可重載的操作符如下表(全部的操作符請點擊 Operator 連結 去官網訪站查看)
符號 | 符號 | 符號 | 符號 |
---|---|---|---|
< | > | <= | = |
- | + | * | / |
~/ | % | [] | []= |
| | & | << | |
~ | == | % |
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");
}
Call 方法:重載呼叫符號
● Dart 有一個 call 函數,它相當於小括號 ()
符號的重載(也就是呼叫符號重載)
透過定義它我們只要使用 ()
就可以省略省去呼叫 call
函數… 使用範例如下
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");
}
Enum 類
● 如同 Java 的枚舉類,每個枚舉類型都有一個 index 的屬性可使用(用來標示元素的位置),並且可以透過 values
來取得 Enum 的集合
Dart Enum 類使用的範例如下:
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 ");
}
Dart 建構函數 constructor
Dart 的建構函數可以使用 可選命名參數({}
符號)、可選位置參數([]
符號)來包裹參數達到函數重載的特性 (因為 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";
}
}
● 使用命名建構函數
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);
}
初始化列表:與 C++ 比較
● 初始化列表:這種初始化列表的使用方式如同 C++ 的建構函數一樣,使用冒號(:
)開頭,後面接續需要賦值的屬性
初始化列表很常被使用在定義私有的成員
使用範例如下:
● 在建構函數後使用初始化列表
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";
}
}
● 使用命名建構函數與初始化列表
void main() {
InfoBean fish = InfoBean.initList("Fish", 19);
print(fish);
}
● Dart 初始化列表與 C++ 初始化列表區別
C++ 11 以後也有初始化列表,不過 C++ 的初始化列表不能使用在類別宣告 (除了 inline 函數),因為 C++ 會把宣告與實現分開,而 Dart 不會
#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 也可以使用 初始化列表的方式呼叫別的建構函數
● 建構函數重新定向
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";
}
}
● 呼叫重定向的建構函數
void main() {
final hey = InfoBean.redirect("Hey", 21);
print(hey);
}
如下圖所見,呼叫重定向的建構函數後,它確被重新導向 InfoBean._nameAge
命名函數中
● 重新定向建構函數限制
A. 重新定向建構函數不可以使用
this
初始化B. 重新定向建構函數不能有方法體
const 建構函數:加強效能
● const 是在編譯期間就確定,並且它也可以說用在建構函數上… 而需要實現這個功能就要一些條件(或是說限制) 1. 使用 const
修飾建構函數,並 2. 聲明類的 所有 成員為 final
,3. 使用時必須以 const
宣告
const 可以減少動態規劃記憶體空間的耗能,提高運行效能
const 建構函數範例如下
A. 所有的成員都要初始化:
以下是個錯誤示範,由於 name
成員沒有被定義,所以不能使用 const
宣告建構函數
// 錯誤示範
class HelloWorld {
final String name;
const HelloWorld();
}
B. 所有的成員皆為 final
描述:
以下是個錯誤示範,雖然建構函數中都有賦予值,但是由於 number
成員沒有被 final
描述,所以也無法建構 const
建構函數
class HelloWorld {
final String name;
int number;
const HelloWorld(this.name, this.number);
}
C. 使用 const
建構函數:用法如同呼叫一般建構函數,但是要注意,若要發揮出 const
建構函數的較能,則呼叫時也要用 const
宣告
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
}
● 如下圖,我們可以發現一件很有趣的事情
使用 const 創建的物件,若是成員數值相同則會被歸類為同一個物件,規劃在同一塊記憶體上
工廠建構函數:factory
● Dart 有提供 factory
關鍵字,讓自動產生一個 工廠設計模式 的方法(想了解更多工廠設計請點擊連結),而 factory
這種提供方式就像是替我們創建了一個靜態方法,而該方法專門產生類的實體(instance
)
但這個產生的實體並非是單例,而是新物件
● factory & static 方法差異
factory 強制返回的就是該類的物件,而 static 則可以返回不同的物件 (或是不返回)
factory
範例如下
// 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}");
}
● 以下使用 Dart 的 factory
實現 單一工廠 模式
實現單例的手法仍然相同,重點就是 1. 私有化建構函數、2. 使用靜態變量保存單例物件、3. 使用靜態方法(而這裡就改成使用 factory
關鍵字)往外提供單例物件
● 實現物件單例:
class Manager {
static Manager? _instance;
// Constructor
// 使用 _<Function name> 可以讓外部函數無法調用
Manager._internal();
factory Manager.getInstance() {
if(_instance == null) {
_instance = new Manager._internal();
}
return _instance!;
}
}
● 使用單例物件
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}");
}
想了解更多單例設計方式,請查看 Singleton 單例模式、設計 | 解說實現 | Android Framework Context Service
由於 Dart 是單執行緒,所以不用擔心同步問題 (不像 java 必須使用
volatile
、synchronized
關鍵字)
● 同樣的,我們也可以使用 factory
關鍵字來實現 覆用工廠
其重點就是 1. 私有化建構函數、2. 使用 Map 變量保存物件、3. 使用靜態方法(而這裡就改成使用 factory
關鍵字)往外提供覆用物件
● 實現覆用物件:
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;
}
}
}
● 使用覆用物件:
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}");
}
想了解夠多的工廠設計方法,請查看 Factory 工廠方法模式 | 解說實現 | Java 集合設計
Dart 抽象設計
Java 的抽象設計有 abstract class
、interface
並且它是單一繼承制度,而 Dart 在物件導向的設計上給予了不同於 Java 的設計(接下來這小節要介紹的)
抽象類 abstract class
● 使用 abstract 修飾符定義抽象類(abstract class
)
abstract class Parent {
// 抽象方法,省略 abstract
void printInfo();
}
這個抽象類如同 Java 抽象類不能實例化
● 繼承抽象類如同 Java 使用關鍵字 extends
,若是有抽象方法就必須實作,並且透過 super 呼叫父類建構函數、方法、成員
範例如下
● 透過 abstract
關鍵字定義抽象類
// 抽象類
abstract class Parent {
String name;
Parent(this.name) {
print('Parent construct');
}
// 抽象方法,省略 abstract
void printInfo();
void description() {
name = "Mr.$name";
}
}
● 定義子類,透過 extends
關鍵字繼承於父類
// 子類
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";
}
}
● 使用抽象類範例:
void main() {
// Parent parent = Parent(); // 不能實體化抽象類
Parent p1 = Child("Alien"); // 可多型
p1.printInfo();
print("p1.runtimeType: ${p1.runtimeType}");
}
● Dart 不支援內部類,Java 才支援內部類
// 內部類 class Hello { // Dart 不支援內部類 class World { } }
介面 implements
● Dart 並沒有 interface
關鍵字,Dart 中每個類都隱式的定義了一個包含實例成員的介面(不管抽象 or 實體類)
● 那要怎麼分辨當前類是使用「繼承」的抽象類,還是使用「實作」的介面類?
這其實要依靠類的實現關鍵字來決定
● 如果實現類要使用繼承類,那就使用
extends
關鍵字● 如果要使用實作介面類,那就要使用
implements
關鍵字
● 接下來我們看實現類如何透過實作,實體類(class
)的介面、抽象類(abstract class
)的介面
● 實作「實體類」的介面
● 定義實體類
由於實體類的方法必須定義方法體,所以以下使用方法的空實現
class IInterface {
// 空實現
void connect() {} // 實體類,所以需要 "{}"
void disconnect() {}
}
● 實現類把實體類作為介面使用
使用 implements
關鍵字實作實體類的介面方法… 由於 IInterface
的方法中有方法體,所以這邊的實現算是覆寫(override
)
class MyClass implements IInterface {
@override
void connect() {
print("connect");
}
@override
void disconnect() {
print("disconnect");
}
}
● 實作「抽象類」的介面
● 定義抽象類
由於使用抽象類,所以抽象方法不需要方法體
abstract class AClass {
// 定義抽象方法
void group();
void item();
}
● 實現類把抽象類作為介面使用
同樣使用 implements
關鍵字來實作抽象類的方法
class Router implements AClass {
@override
void group() {
print("group");
}
@override
void item() {
print("item");
}
}
混合類 mixin:類多重繼承/菱形問題
● Dart 與一般的高級語言(像是 Java
、Kotlin
、Swift
)不同的特點之一就是「多重繼承」(但 Dart 其實並非真正實現多重繼承,而是使用 Mixins 手動來達成「類似多重繼承」的效果)
Dart 以 mixin
關鍵字來定義「混合類」,並以 with
關鍵字來使用混合類
混合類範例如下
// 使用 mixin 關鍵字定義混合類
mixin class Hostel {
String describe() {
return "Hostel";
}
}
// 配合 `with` 關鍵字就可以使用混合類
class Voyage with Hostel {
}
● 當然
mixin
關鍵字不只可以使用在類上,以可以在另一个 mixin 中使用 mixinmixin 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 類的限制如下所示
● 混合類除了空建構函數之外,不能有有參建構函數
mixin class Hostel {
final String address;
Hostel(this.address);
String describe() {
return "Hostel";
}
}
● 混合類除了 Object 類以外,不能在繼承其它類,利用這個規則來避免多重繼承的菱形問題!
class Coffee {
String describe() {
return "Coffee";
}
}
mixin class Hostel extends Coffee { // 混合類不能再繼承其他類!
String describe() {
return "Hostel";
}
}
● 什麼是菱形問題?混合類(
mixin class
)有多重繼承的菱形問題?在多重繼承裡有個較為麻煩的「菱形問題」:菱形問題是說,假設繼承兩個混合類,而這兩個混合類中又有相同的方法,那呼叫時到底該定位到哪個實現呢?
傳統多重繼承導致的菱形問題概念圖如下(下圖問題不會出現在 Dart 中)
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
時該定位到哪個方法呢?
● 對於這個問題,Dart 使用了 方法解析順序(Method Resolution Order
, MRO) 來確保在使用 mixin
和 mixin class
時,方法的調用順序是可預測的和一致的,從而解決了多重繼承定位方法的問題
使用範例(測試)如下
● 定義多個 mixin class
,並類這些混合類中都擁有相同的方法
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()
),不過 繼承的順序不同!
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()}");
}
}
● 測試多重混合繼承後,對於相同的方法會被定位到哪類上
void main() {
print("Use VoyageFinalHostel");
VoyageFinalHostel().printMsg();
print("\nUse VoyageFinalCoffee");
VoyageFinalCoffee().printMsg();
}
如下圖所見,我們可以看到混合方法會依照順序被覆蓋,由於 VoyageFinalHostel
類最後繼承的混合類是 Hostel
,所以 describe()
就會定位到 Hostel;而 VoyageFinalCoffee
類最後的繼承混合類是 Coffee
,所以會定位到 Coffee
更多的 Flutter/Dart 語言相關文章
了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!
探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台
Dart 語言基礎
● 探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南
快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。
● Dart 函數與方法、異常處理、引用庫 | Java 比較
深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。
● 深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性
學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。
● 深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream
徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!
深入 Flutter 框架
● 深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫
從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。
● 深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案
探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。
● 深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南
拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!