博客 / 詳情

返回

dart中,mixin的用法

一、初識 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!

解釋:

  1. Logger 是一個 mixin,它提供了一個 log 方法。
  2. UserAdmin 類都使用了 Logger 作為 mixin,並複用了 log 方法。
  3. 通過 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

三、實際場景(更復雜的應用)

場景:我們以複雜的表格組件為例,如果把整個組件寫到一個文件裏面,顯然是很不合理的,所以我們必須拆分,從直觀的感受上,我們假設分解出:ColumnStateRowStateCellState 分別針對表格的列、行、單元格,這裏只是截取部分邏輯,真實場景遠比此複雜。

設計目標:把這三個對象的相關操作合併到一個類裏面,對外提供所有的操作,並且,這三個類之間還需要相互調用方法,當然,除了這三個類之外,還可能需要融入其他的類操作。

為了抽象出這三類對象,我們分別設計接口(IColumnStateIRowStateICellState),分別針對設計出對應的mixinColumnStateRowStateCellState):裏面定義出針對三類對象需要的方法,為了簡便起見,每種接口只設計一個方法;


/// 針對 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
答:因為該接口意在申明所有接口(IColumnStateIRowStateICellState)包含的方法,甚至還融入了其他實際類(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();
}

如果你用傳統的classimplements實現該接口,那麼必須實現裏面所有的方法,如下:

class MyClassA implements IInterfaceA {
  @override
  void doSomething_1() {
    print('MyClass doSomething_1');
  }

  @override
  void doSomething_2() {
    print('MyClass doSomething_2');
  }
}

如果你用mixinimplements實現該接口,那麼你可以實現一部分接口或成員,或者全部不實現,把實現的部分交給後面withmixin的類來實現:

定義一個mixinimplements該接口(我只實現了一個接口——即使一個都不實現也不會有問題):

/// MyMixinA 實現了接口 IInterfaceA,但是隻 override 了一個方法
mixin MyMixinA implements IInterfaceA {
  @override
  void doSomething_1() {}
}

定義一個classwithmixin(需要實現: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 可以通過以下兩種方式實現:

  1. 提供一個 getter 方法體(如 get list => ...)
  2. 提供一個同名的實例變量(如 final List<String> list;)

設計思路:
1、IInterfaceA接口定義了一套規劃,需要的屬性,針對屬性的操作方法;
2、mixin MyMixinA通過實現接口,定義具體的方法內容;
3、實際類MyClassB with MyMixinA,將未override的getter賦值(通過構造函數後面的:);

程序執行順序就是,先給list賦值,然後方法裏面需要針對該getter都可以順利執行了。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.