Dart Sealed Class 与模式匹配实战指南
 · 9 min read
在 Flutter 应用开发中,我们经常需要处理多种状态和类型的数据。例如,一个网络请求可能处于加载中、成功、失败等不同状态。传统的做法可能使用枚举或继承体系来处理这些情况,但往往会面临类型安全和维护性的挑战。
Dart 3.0 引入的 sealed class 和增强的模式匹配特性,为这类问题提供了更优雅的解决方案。其实我们已经在前面的文章《Flutter 优化 API 错误响应:Result 模式实践指南》 中提过 sealed class ,并给出了具体的实现方式,但是没有针对这个新语法做更多的深入探索。本文将延续这一话题,让我们通过实际案例来更加深入理解这些特性。
版本要求
- Dart SDK: 3.0 或更高版本
 - Flutter: 3.10 或更高版本
 
Sealed Class 基础
sealed class 是一种特殊的抽象类,它限定了可能的子类集合。这种限制在编译时就能确保所有可能的类型都被正确处理。让我们看一个实际的例子:
sealed class ApiResponse {
  const ApiResponse();
}
final class Loading extends ApiResponse {
  final DateTime startTime;
  const Loading({required this.startTime});
}
final class Success extends ApiResponse {
  final dynamic data;
  final DateTime timestamp;
  const Success({required this.data, required this.timestamp});
}
final class Failure extends ApiResponse {
  final String message;
  final int statusCode;
  final DateTime timestamp;
  const Failure({
    required this.message, 
    required this.statusCode, 
    required this.timestamp
  });
}
这个设计有几个关键优点:
- 类型安全: 所有 ApiResponse 的可能类型都在编译时确定
 - 穷尽性检查: 编译器确保处理所有可能的情况
 - 封闭性: 防止在其他文件中创建新的子类
 
模式匹配详解
Dart 3.0 引入的模式匹配让我们能更优雅地处理 sealed class。以下展示两种主要的使用方式:
1. switch-case 模式
String handleResponse(ApiResponse response) => switch(response) {
  Loading(startTime: var time) => 
    'Loading since ${time.toString()}',
  
  Success(data: final d, timestamp: var ts) => 
    'Success: $d at ${ts.toString()}',
  
  Failure(message: var msg, statusCode: var code) => 
    'Error $code: $msg'
};
2. if-case 模式
void showResponseUI(ApiResponse response) {
  if (response case Loading()) {
    showLoadingSpinner();
  } else if (response case Success(data: var data)) {
    showData(data);
  } else if (response case Failure(message: var msg)) {
    showError(msg);
  }
}
实战示例: API 客户端实现
让我们实现一个完整的 API 客户端示例:
class ApiClient {
  Future<ApiResponse> fetchData() async {
    try {
      final startTime = DateTime.now();
      // 发出加载状态
      yield Loading(startTime: startTime);
      
      final response = await http.get(Uri.parse('https://api.example.com/data'));
      
      if (response.statusCode == 200) {
        return Success(
          data: jsonDecode(response.body),
          timestamp: DateTime.now()
        );
      } else {
        return Failure(
          message: 'Request failed',
          statusCode: response.statusCode,
          timestamp: DateTime.now()
        );
      }
    } catch (e) {
      return Failure(
        message: e.toString(),
        statusCode: -1,
        timestamp: DateTime.now()
      );
    }
  }
}
传统方案对比
枚举方案
enum ApiState { loading, success, failure }
class ApiResult {
  final ApiState state;
  final dynamic data;
  final String? error;
  
  ApiResult({
    required this.state,
    this.data,
    this.error,
  });
}
问题:
- 类型不安全,data 和 error 可能同时为 null
 - 状态和数据耦合在一起
 - 难以添加状态特有的属性
 - 运行时可能出现状态和数据不一致
 
传统继承方案
abstract class ApiResult {
  const ApiResult();
}
class ApiSuccess extends ApiResult {
  final dynamic data;
  ApiSuccess(this.data);
}
class ApiError extends ApiResult {
  final String message;
  ApiError(this.message);
}
问题:
- 无法在编译时保证子类的完整性
 - 其他开发者可以随意添加新的子类
 - 需要手动处理所有类型检查
 - switch 语句不会强制检查所有情况
 
高级应用场景
嵌套 Sealed Classes
sealed class NetworkEvent {}
sealed class AuthEvent extends NetworkEvent {
  final DateTime timestamp;
  const AuthEvent(this.timestamp);
}
final class LoginSuccess extends AuthEvent {
  final String token;
  const LoginSuccess(this.token, DateTime timestamp) : super(timestamp);
}
final class LoginFailure extends AuthEvent {
  final String reason;
  const LoginFailure(this.reason, DateTime timestamp) : super(timestamp);
}
final class NetworkError extends NetworkEvent {
  final String message;
  const NetworkError(this.message);
}
// 使用嵌套模式匹配
String handleNetworkEvent(NetworkEvent event) => switch(event) {
  AuthEvent() => switch(event) {
    LoginSuccess(token: var t) => 'Logged in: $t',
    LoginFailure(reason: var r) => 'Login failed: $r',
  },
  NetworkError(message: var m) => 'Network error: $m',
};
状态机实现
sealed class TrafficLight {
  final Duration duration;
  const TrafficLight(this.duration);
  
  TrafficLight next();
}
final class RedLight extends TrafficLight {
  const RedLight() : super(const Duration(seconds: 30));
  
  
  TrafficLight next() => const GreenLight();
}
final class YellowLight extends TrafficLight {
  const YellowLight() : super(const Duration(seconds: 3));
  
  
  TrafficLight next() => const RedLight();
}
final class GreenLight extends TrafficLight {
  const GreenLight() : super(const Duration(seconds: 20));
  
  
  TrafficLight next() => const YellowLight();
}
泛型支持
sealed class Result<T> {
  const Result();
}
final class Success<T> extends Result<T> {
  final T value;
  const Success(this.value);
}
final class Failure<T> extends Result<T> {
  final String error;
  const Failure(this.error);
}
事件驱动架构
sealed class UserEvent {}
final class UserCreated extends UserEvent {
  final String userId;
  final DateTime createdAt;
  const UserCreated(this.userId, this.createdAt);
}
final class UserUpdated extends UserEvent {
  final String userId;
  final Map<String, dynamic> changes;
  const UserUpdated(this.userId, this.changes);
}
final class UserDeleted extends UserEvent {
  final String userId;
  final String reason;
  const UserDeleted(this.userId, this.reason);
}
class EventProcessor {
  void processEvent(UserEvent event) => switch(event) {
    UserCreated(userId: var id, createdAt: var time) => 
      logUserCreation(id, time),
    UserUpdated(userId: var id, changes: var changes) => 
      applyUserChanges(id, changes),
    UserDeleted(userId: var id, reason: var reason) => 
      handleUserDeletion(id, reason),
  };
}
响应式编程集成
abstract class BlocState {}
sealed class DataState<T> extends BlocState {
  const DataState();
}
final class Initial<T> extends DataState<T> {
  const Initial();
}
final class Loading<T> extends DataState<T> {
  final T? previousData;
  const Loading([this.previousData]);
}
final class Success<T> extends DataState<T> {
  final T data;
  const Success(this.data);
}
final class Error<T> extends DataState<T> {
  final String message;
  final T? previousData;
  const Error(this.message, [this.previousData]);
}
// 在 StreamBuilder 中使用
Widget buildState<T>(DataState<T> state) => switch(state) {
  Initial<T>() => const LoadingPlaceholder(),
  Loading<T>(previousData: var data) => 
    LoadingOverlay(child: DataWidget(data: data)),
  Success<T>(data: var d) => DataWidget(data: d),
  Error<T>(message: var msg, previousData: var data) => 
    ErrorWidget(message: msg, fallbackData: data),
};
这些高级用法展示了 sealed class 在复杂系统中的强大能力:
- 类型安全的状态管理
 - 可组合的事件处理
 - 清晰的领域模型
 - 灵活的泛型支持
 - 优雅的模式匹配
 
性能考量
sealed class 的性能影响主要体现在以下几个方面:
- 编译时:
 
- 模式匹配的穷尽性检查会增加编译时间
 - 但这个开销在实际项目中可以忽略不计
 
- 运行时:
 
- switch 表达式被编译为高效的跳转表
 - 相比传统的 instanceof 检查更高效
 - 内存占用与普通类继承相同
 
最佳实践
- 状态建模:
 
- 使用 sealed class 对有限状态集合进行建模
 - 确保状态之间的转换是显式的和类型安全的
 
- 错误处理:
 
- 在 Failure 类中包含足够的错误信息
 - 考虑添加堆栈跟踪以便调试
 
- 测试策略:
 
- 为每个状态编写单元测试
 - 测试状态转换的边界情况
 - 验证错误处理逻辑
 
总结
sealed class 结合模式匹配为 Dart 开发带来了更强的类型安全性和更清晰的代码结构。通过本文的示例和最佳实践,您应该能够在实际项目中有效地运用这些特性。建议从小范围试点开始,逐步在更多场景中应用这种模式。