一、初識 mixin
在 Dart 中,mixin 是一種複用代碼的方式,允許將類的功能共享給多個類,而不需要通過繼承的方式。它提供了一種輕量級的方式來讓多個類共享相同的行為,而不必強制要求它們有共同的父類。
mixin 的作用:
- 避免多重繼承:
Dart不支持多重繼承,但是mixin可以讓你在多個類之間共享代碼,從而避免了多重繼承帶來的複雜性。 - 代碼複用:通過
mixin,你可以將一組通用的功能封裝起來,在不同的類中複用,而不需要修改類的繼承結構。 - 增強類的功能:可以為已有的類添加新的功能,而不需要改變類的繼承關係。
一般用於的場景:
- 多個類需要共享某些相同的功能或行為時,可以使用
mixin來複用這些功能。 - 當類之間的功能不具有父子關係,但又需要複用某些功能時,
mixin是一個理想的選擇。
示例:
假設你有多個類需要實現日誌功能,你可以通過 mixin 來共享這個功能:
// 定義一個日誌功能的mixin
mixin Logger {
void log(String message) {
print('Log: $message');
}
}
// 定義一個使用Logger mixin的類
class User with Logger {
String name;
User(this.name);
void greet() {
log('User $name greeted');
print('Hello, $name!');
}
}
// 定義另一個使用Logger mixin的類
class Admin with Logger {
String name;
Admin(this.name);
void greet() {
log('Admin $name greeted');
print('Hello, admin $name!');
}
}
void main() {
var user = User('Alice');
user.greet();
var admin = Admin('Bob');
admin.greet();
}
輸出:
Log: User Alice greeted
Hello, Alice!
Log: Admin Bob greeted
Hello, admin Bob!
解釋:
Logger是一個mixin,它提供了一個log方法。User和Admin類都使用了Logger作為mixin,並複用了log方法。- 通過
with Logger關鍵字將Logger混入到類中,實現了代碼的複用。
小結:
mixin適用於多個類之間共享行為,避免了繼承帶來的複雜性。- 它幫助你提高代碼的可複用性,尤其在沒有父類關係的情況下,可以避免重複編寫相同的功能代碼。
二、進階
mixin 後面跟的是 mixin 名稱,而不是傳統意義上的“類名”。雖然它的定義方式類似於類的定義,但 mixin 本身並不是類,而是一種用來共享功能的代碼塊。
在 Dart 中,mixin 是一個可以被多個類複用的功能集,但它並不代表一個單獨的類。如果你把它理解為一個“類名”也可以,但更準確的説法是它是一個功能集合。
但是, mixin 後面可以跟 implements 關鍵字,是因為在 Dart 中,mixin 可以實現接口(implements)。這意味着你可以通過 mixin 來提供某些功能,並且確保這個 mixin 必須遵循某個接口的結構。
// 定義一個接口
abstract class IInterface {
void doSomething();
}
// 定義一個mixin,並實現接口
mixin Logger implements IInterface {
void log(String message) {
print('Log: $message');
}
@override
void doSomething() {
print('Doing something from IInterface');
}
}
// 定義一個類,使用Logger mixin
class User with Logger {
String name;
User(this.name);
void greet() {
log('User $name greeted');
print('Hello, $name!');
}
}
void main() {
var user = User('Alice');
user.greet();
user.doSomething(); // 調用mixin中的doSomething方法
}
輸出:
Log: User Alice greeted
Hello, Alice!
Log: Doing something from IInterface
三、實際場景(更復雜的應用)
場景:我們以複雜的表格組件為例,如果把整個組件寫到一個文件裏面,顯然是很不合理的,所以我們必須拆分,從直觀的感受上,我們假設分解出:ColumnState、RowState、CellState 分別針對表格的列、行、單元格,這裏只是截取部分邏輯,真實場景遠比此複雜。
設計目標:把這三個對象的相關操作合併到一個類裏面,對外提供所有的操作,並且,這三個類之間還需要相互調用方法,當然,除了這三個類之外,還可能需要融入其他的類操作。
為了抽象出這三類對象,我們分別設計接口(IColumnState、IRowState。ICellState),分別針對設計出對應的mixin(ColumnState、RowState、CellState):裏面定義出針對三類對象需要的方法,為了簡便起見,每種接口只設計一個方法;
/// 針對 Column 設計的接口
abstract class IColumnState {
void doSomethingColumn();
}
/// 使用 mixin 關鍵字,對外提供 IColumnState 接口的具體實現方法。
/// 為什麼需要實現 IAllState?因為該接口包含了所有的接口的方法,而該類中有方法需要調用其他接口下的方法。
mixin ColumnState implements IAllState {
@override
void doSomethingColumn() {
print('來自 IColumnState 接口的 實現方法');
/// 調用了其他接口的方法
doSomethingRow();
doSomethingCell();
}
}
/// 針對 Row 設計的接口
abstract class IRowState {
void doSomethingRow();
}
/// 使用 mixin 關鍵字,對外提供 IRowState 接口的具體實現方法
mixin RowState implements IAllState {
@override
void doSomethingRow() {
print('來自 IRowState 接口的 實現方法');
}
}
/// 針對 Cell 設計的接口
abstract class ICellState {
void doSomethingCell();
}
/// 使用 mixin 關鍵字,對外提供 ICellState 接口的具體實現方法
mixin CellState implements IAllState {
@override
void doSomethingCell() {
print('來自 ICellState 接口的 實現方法');
}
}
/// 其他類
class TestClass {
void onTest() {
print('來自其他類實際類的 具體方法');
}
}
/// 定義一個包含所有對象的接口(除了包含了接口IColumnState, IRowState, ICellState,還包含了其他類 TestClass)
/// 注意:抽象類實現具體類TestClass,只是包含了簽名(不包含方法體);,所以 TabStateChangeNotifier 需要 繼承 TestClass
abstract class IAllState
implements TestClass, IColumnState, IRowState, ICellState {}
/// 定義一個包含所有對象的類,對外提供接口 IAllState 包含的所有內容
class TabStateChangeNotifier extends TestClass
with ColumnState, RowState, CellState {
/// 構造函數
TabStateChangeNotifier();
void greet() {
onTest();
print('來自統一對外類自己的方法');
}
}
具體使用:
var manager = TabStateChangeNotifier();
manager.greet();
manager.doSomethingColumn();
輸出:
來自其他類實際類的 具體方法
來自統一對外類自己的方法
來自 IColumnState 接口的 實現方法
來自 IRowState 接口的 實現方法
來自 ICellState 接口的 實現方法
注意點:
1、為什麼需要定義接口IAllState?
答:因為該接口意在申明所有接口(IColumnState、IRowState、ICellState)包含的方法,甚至還融入了其他實際類(TestClass)的方法。
2、為什麼mixin 名稱需要實現IAllState,而不是對應的接口(mixin ColumnState 實現 IAllState,而不是實現IColumnState)?
答:因為mixin ColumnState裏面有一些方法需要調用其他接口下方法,如方法doSomethingColumn()調用了doSomethingRow()、doSomethingCell()。
3、接口IAllState定義的內容和TabStateChangeNotifier具體實現是一一對應的,即前者包含了多少方法、getter、setter,那麼TabStateChangeNotifier都會有對應的實現,具體説明:抽象類IAllState實現了其他類TestClass(只是包含了方法簽名,不包含方法體),以及接口IColumnState, IRowState, ICellState,所以具體實現類TabStateChangeNotifier也需要繼承TestClass類的實現,並且通過with混入三個接口(IColumnState, IRowState, ICellState)對應的實現(ColumnState, RowState, CellState),
如此下來,通過mixin 名稱共享代碼塊,其他類通過with來混入定義的代碼塊,這樣就很好的把代碼拆分到不同的文件,有很方便組裝出強大的功能,從而達到代碼複用的效果;
四、mixin的優勢
mixin的優勢在於靈活,接下來將一個實際的場景,用來説明mixin的靈活應用。
假設我定義了一個接口:
abstract class IInterfaceA {
void doSomething_1();
void doSomething_2();
}
如果你用傳統的class來implements實現該接口,那麼必須實現裏面所有的方法,如下:
class MyClassA implements IInterfaceA {
@override
void doSomething_1() {
print('MyClass doSomething_1');
}
@override
void doSomething_2() {
print('MyClass doSomething_2');
}
}
如果你用mixin來implements實現該接口,那麼你可以實現一部分接口或成員,或者全部不實現,把實現的部分交給後面with該mixin的類來實現:
定義一個mixin來implements該接口(我只實現了一個接口——即使一個都不實現也不會有問題):
/// MyMixinA 實現了接口 IInterfaceA,但是隻 override 了一個方法
mixin MyMixinA implements IInterfaceA {
@override
void doSomething_1() {}
}
定義一個class來with該mixin(需要實現:MyMixinA 實現 IInterfaceA 沒有 override 的部分成員)
/// 因為 MyMixinA 實現了接口 IInterfaceA,但是隻 override 一個方法 doSomething_1.
/// 所有該類需要 override 另一個方法 doSomething_2.
class MyClassB with MyMixinA {
@override
void doSomething_2() {}
}
最終代碼(實際應用例子):
我們可以設計一個接口(一套規則),裏面有一個集合,然後針對集合操作的方法,通過mixin實現具體實現的邏輯部分(方法),而把“集合”賦值部分放到最終的類(with mixin的類)來實現。
abstract class IInterfaceA {
/// 定義集合訪問器(在最終類裏面實現)
List<String> get list;
/// 打印 list 長度
void doSomething_1();
/// 打印 list 中的第一個元素
void doSomething_2();
}
/// 定義 mixin 實現 接口 IInterfaceA(只從寫了兩個方法)
mixin MyMixinA implements IInterfaceA {
@override
void doSomething_1() {
print('數組長度:${list.length}');
}
@override
void doSomething_2() {
String str = list.map((s) => s).join(', ');
print('數組內容:${str}');
}
}
/// 定義類 MyClassB with MyMixinA(需要override MyMixinA中未override的成員)
class MyClassB with MyMixinA {
MyClassB() : list = ['a', 'b', 'c'];
@override
final List<String> list;
}
需要注意的是,接口IInterfaceA定義的是List<String> get list,看上去是一個get屬性,但是MyClassB override 的寫法是final List<String> list;
這是因為:抽象 getter 可以通過以下兩種方式實現:
- 提供一個 getter 方法體(如 get list => ...)
- 提供一個同名的實例變量(如 final List<String> list;)
設計思路:
1、IInterfaceA接口定義了一套規劃,需要的屬性,針對屬性的操作方法;
2、mixin MyMixinA通過實現接口,定義具體的方法內容;
3、實際類MyClassB with MyMixinA,將未override的getter賦值(通過構造函數後面的:);
程序執行順序就是,先給list賦值,然後方法裏面需要針對該getter都可以順利執行了。