一、枚举不只是简单的值容器
很多人刚开始用Dart枚举时,只是把它当作一个简单的值容器。比如这样:
enum HttpStatus {
success,
notFound,
serverError
}
这确实能用,但就像只用了手机的通话功能一样浪费。Dart枚举实际上是个隐藏的瑞士军刀,今天我们就来解锁它的高级玩法。
来看个更有意思的例子:
enum NetworkResponse with LoggerMixin {
success(200, 'OK') {
// 可以给特定枚举值添加方法
void cacheResponse() => print('Caching successful response');
},
notFound(404, 'Not Found'),
serverError(500, 'Internal Server Error');
final int code;
final String description;
const NetworkResponse(this.code, this.description);
// 公共方法
void log() => print('$code: $description');
}
看到没?枚举可以:
- 有构造函数和属性
- 实现mixin
- 为特定值添加专属方法
- 定义公共方法
二、模式匹配:枚举的真正威力
Dart 3.0引入的模式匹配让枚举真正起飞了。来看个电商订单状态的例子:
enum OrderStatus {
pending,
processing,
shipped,
delivered,
cancelled;
// 用switch表达式处理不同状态
String get statusMessage => switch(this) {
pending => '您的订单已接收',
processing => '正在准备发货',
shipped => '已发货,物流更新中',
delivered => '订单已完成',
cancelled => '订单已取消',
};
// 更复杂的模式匹配
void handleStatus() {
switch(this) {
case OrderStatus.pending:
sendNotification();
startProcessingTimer();
case OrderStatus.processing:
checkInventory();
case OrderStatus.shipped when DateTime.now().hour > 17:
scheduleDeliveryUpdateForTomorrow();
case OrderStatus.shipped:
scheduleDeliveryUpdate();
case OrderStatus.delivered:
requestReview();
case OrderStatus.cancelled:
issueRefund();
collectFeedback();
}
}
}
模式匹配的妙处在于:
- 编译器会检查是否覆盖所有情况
- 可以使用when条件进行更精细的控制
- 代码可读性极高,业务逻辑一目了然
三、枚举与JSON的完美配合
实际开发中经常需要处理JSON序列化。看这个用户权限的例子:
enum UserRole {
guest(0),
user(1),
moderator(2),
admin(3);
final int level;
const UserRole(this.level);
// JSON序列化
factory UserRole.fromJson(dynamic json) {
if (json is int) {
return values.firstWhere(
(role) => role.level == json,
orElse: () => guest,
);
} else if (json is String) {
return values.firstWhere(
(role) => role.name == json.toLowerCase(),
orElse: () => guest,
);
}
return guest;
}
dynamic toJson() => level; // 也可以选择输出name
}
// 使用示例
void main() {
final json = 2;
final role = UserRole.fromJson(json);
print(role); // 输出: UserRole.moderator
print(role.toJson()); // 输出: 2
}
这样处理的好处是:
- 数据库存储可以用更小的int类型
- 前后端交互灵活,支持数字和字符串格式
- 类型安全,避免魔法数字
四、高级技巧:状态机实现
枚举特别适合实现状态机。来看个下载器的例子:
enum DownloadState {
idle,
connecting,
downloading(double progress), // 带参数的状态
paused,
completed,
failed(String error);
// 状态转换逻辑
DownloadState transition(dynamic event) {
switch(this) {
case idle:
if (event == 'start') return connecting;
case connecting:
if (event == 'connected') return downloading(0.0);
if (event is String) return failed(event);
case downloading(progress):
if (event == 'pause') return paused;
if (event == 'complete') return completed;
if (event is double) return downloading(event);
if (event is String) return failed(event);
case paused:
if (event == 'resume') return downloading(0.0);
if (event == 'cancel') return idle;
case completed || failed:
if (event == 'restart') return idle;
}
return this; // 保持当前状态
}
}
// 使用示例
void main() {
var state = DownloadState.idle;
state = state.transition('start');
state = state.transition('connected');
if (state case downloading(progress: var p)) {
state = state.transition(0.5); // 更新进度
}
print(state); // 输出: downloading(0.5)
}
这种实现方式:
- 所有状态转换一目了然
- 非法状态转换会被自动阻止
- 可以携带状态数据(如下载进度)
- 编译器会检查是否处理了所有情况
五、实际应用中的注意事项
虽然枚举很强大,但也要注意几点:
性能考虑:大量使用模式匹配可能影响性能,关键路径要测试
可维护性:当枚举值超过10个时,考虑是否应该拆分成多个枚举
序列化陷阱:
// 错误示例:忘记处理null情况
UserRole.fromJson(json['role'] ?? 'guest'); // 安全写法
- 测试技巧:
test('测试所有状态', () {
for (final status in OrderStatus.values) {
expect(status.statusMessage, isNotEmpty);
}
});
- 与Freezed配合:考虑使用freezed包生成更强大的枚举类
六、总结
Dart枚举远比你想象的强大。通过本文你应该学会了:
- 给枚举添加属性和方法
- 利用模式匹配处理复杂逻辑
- 实现类型安全的JSON序列化
- 构建清晰的状态机
- 避免常见的坑
下次当你需要表示一组固定的状态或类型时,别再简单用字符串或数字了,试试这些枚举的高级用法,让你的代码更安全、更易读、更易维护!
评论