一、初识 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
都可以顺利执行了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。