Mixin 基础
Mixin 声明
mixin Logger {
void log(String message) {
print('Log: $message');
}
void error(String message) {
print('Error: $message');
}
}
mixin TimeStamp {
String getTimestamp() {
return DateTime.now().toIso8601String();
}
}
Mixin 使用
class Service with Logger, TimeStamp {
void doSomething() {
log('Operation started at ${getTimestamp()}');
// 执行操作
log('Operation completed');
}
}
Mixin 约束
on 关键字
// 限制只能在特定类型上使用
mixin CanFly on Bird {
void fly() {
print('Flying high!');
}
}
class Bird {
void chirp() {
print('Chirp chirp!');
}
}
class Eagle extends Bird with CanFly {
// 可以使用 fly 方法
}
class Dog with CanFly { // 错误: Dog 不是 Bird 的子类
// 编译错误
}
多个 Mixin
mixin A {
String getMessage() => 'A';
}
mixin B {
String getMessage() => 'B';
}
class C with A, B {
// 使用最后一个 mixin 的实现
// getMessage() 返回 'B'
}
扩展方法
基本语法
extension StringExtension on String {
bool isValidEmail() {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
}
String capitalize() {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
}
// 使用扩展方法
void main() {
print('[email protected]'.isValidEmail()); // true
print('hello'.capitalize()); // Hello
}
泛型扩展
extension ListExtension<T> on List<T> {
T? firstWhereOrNull(bool Function(T) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
List<T> sortedBy(Comparable Function(T) keyExtractor) {
var copy = [...this];
copy.sort((a, b) => keyExtractor(a).compareTo(keyExtractor(b)));
return copy;
}
}
完整示例
// 基础日志 mixin
mixin BaseLogger {
void log(String message) {
print('${DateTime.now()}: $message');
}
}
// 文件操作 mixin:用到了 BaseLogger 的 log,所以用 on 声明依赖
mixin FileOperations on BaseLogger {
Future<void> writeToFile(String path, String content) async {
log('Writing to file: $path'); // 可以使用 BaseLogger 的方法
// 文件写入逻辑
}
Future<String> readFromFile(String path) async {
log('Reading from file: $path');
// 文件读取逻辑
return 'File content';
}
}
// 数据验证 mixin
mixin DataValidator {
bool validateEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
bool validatePhone(String phone) {
return RegExp(r'^\+?[\d\s-]{10,}$').hasMatch(phone);
}
}
// 使用多个 mixin
class UserService with BaseLogger, FileOperations, DataValidator {
Future<void> saveUser(Map<String, String> userData) async {
// 验证数据
if (!validateEmail(userData['email']!)) {
log('Invalid email address');
return;
}
if (!validatePhone(userData['phone']!)) {
log('Invalid phone number');
return;
}
// 保存数据
await writeToFile('users.json', userData.toString());
log('User data saved successfully');
}
}
// 扩展方法示例
extension MapExtension<K, V> on Map<K, V> {
Map<K, V> merge(Map<K, V> other) {
return {...this, ...other};
}
// 按 key 路径逐层取值,取不到返回 null;叶子值类型不确定,所以返回 dynamic
dynamic getPath(List<K> path) {
dynamic current = this;
for (final key in path) {
if (current is Map) {
current = current[key];
} else {
return null;
}
}
return current;
}
}
void main() async {
// 使用 UserService
final userService = UserService();
await userService.saveUser({
'email': '[email protected]',
'phone': '+1234567890',
});
// 使用扩展方法
final map1 = {'a': 1, 'b': 2};
final map2 = {'c': 3, 'd': 4};
final merged = map1.merge(map2);
print(merged); // {a: 1, b: 2, c: 3, d: 4}
final nested = {
'user': {
'address': {
'city': 'New York'
}
}
};
print(nested.getPath(['user', 'address', 'city'])); // New York
}
高级用法
条件 Mixin
mixin DebugLogger on Object {
void debugLog(String message) {
assert(() {
print('Debug: $message');
return true;
}());
}
}
扩展运算符
extension IterableExtension<T> on Iterable<T> {
T? get firstOrNull => isEmpty ? null : first;
Map<K, List<T>> groupBy<K>(K Function(T) keyExtractor) {
var result = <K, List<T>>{};
for (var element in this) {
var key = keyExtractor(element);
(result[key] ??= []).add(element);
}
return result;
}
}
最佳实践
- 合理使用 Mixin 组合功能
- 避免 Mixin 之间的冲突
- 为扩展方法提供文档
- 使用有意义的命名
- 注意扩展方法的作用域
注意事项
- Mixin 不支持构造函数
- 注意 Mixin 的顺序
- 避免过度使用扩展方法
- 处理好命名冲突
- 考虑扩展方法的性能影响
实战案例:一个真实在用的 capitalize()
本文 84 行那个 StringExtension 的例子不是我编的——我的 ygking_json_to_dart_model 项目里真有这么一行:
// lib/services/json_converter.dart:嵌套 JSON 对象生成嵌套类时
$key!.add(${key.capitalize()}.fromJson(v));
场景是:JSON 里 "user": {...},我要生成一个类名叫 User 的 Dart 类。JSON 的 key 是小驼峰,Dart 类名要大驼峰,key.capitalize() 就是干这个的。
为什么用扩展方法而不用普通函数?对比一下:
// 普通函数写法
add(capitalize(key).fromJson(v));
// 扩展方法写法
add(key.capitalize().fromJson(v));
扩展方法的阅读顺序是从左到右的链式:key → 首字母大写 → fromJson。普通函数是嵌套的,要从里往外读。在生成器这种”一句话里做好几件事”的代码里,扩展方法的链式写法可读性明显更好。
但我也给自己定了条规矩:扩展方法只给”通用小操作”用(字符串处理、集合判空这种),业务逻辑一律写成普通方法。原因很实际——扩展方法是全局生效的,String 上挂个 capitalize() 没人会奇怪;但如果在 String 上挂个 toOrderModel(),三个月后你自己都忘了它藏在哪。这条线守住了,扩展方法才是利器,守不住就是灾难。
总结
Mixin 解决”多个类共享一段行为”,扩展方法解决”不改源码给现有类加能力”。两个都别滥用:mixin 记得用 on 写清依赖,扩展方法只收通用小操作——具体分寸看上面的实战案例。
