一、初识 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都可以顺利执行了。


qngyun1029
980 声望15 粉丝