flutter中,到底在什么场景下适合使用mixin?

92 阅读4分钟

可以总结为以下原则,我称之为"CRABS原则"(Composition复合, Reusability复用, Across横切, Behavior行为, Stateful状态):

  1. 当需要在多个类中复用代码,但不适合用继承时(Composition原则)

    • 这些类可能已经有了父类
    • 需要组合多个功能
    • 功能是可选的,不是类的核心特性
  2. 当代码模式在项目中重复出现时(Reusability原则)

    • 表单验证逻辑
    • 加载状态管理
    • 错误处理模式
    • 资源释放模式
  3. 当功能是横切关注点时(Across原则)

    • 日志记录
    • 性能监控
    • 权限检查
    • 缓存管理
    • 主题适配
  4. 当需要为类添加能力或行为时(Behavior原则)

    • 手势处理
    • 动画控制
    • 生命周期行为
    • 网络状态感知
  5. 当需要管理状态或资源时(Stateful原则)

    • 控制器管理
    • 订阅管理
    • 缓存状态
    • 加载状态

判断是否使用Mixin的快速检查表:

// 如果你的代码符合以下情况,就应该考虑使用Mixin:

✓ 这段代码会在多个不相关的类中使用
✓ 这个功能是可选的,不是核心功能
✓ 这段代码不依赖于类的具体实现
✓ 这个功能需要和其他功能组合
✓ 这段代码是一个横切关注点
✓ 这个功能是一种能力或行为的扩展
✓ 这段代码管理着某种状态或资源

// 相反,如果符合以下情况,可能不适合使用Mixin:

✗ 功能是类的核心特性
✗ 代码强依赖于类的具体实现
✗ 功能之间有强耦合关系
✗ 只在单个类中使用
✗ 需要维护复杂的状态

决策树

是否需要Mixin?
├── 是否需要组合多个独立功能?(Composition)
│   ├── 是 → 使用Mixin
│   └── 否 → 继续检查
├── 是否需要在多个地方复用代码?(Reusability)
│   ├── 是 → 使用Mixin
│   └── 否 → 继续检查
├── 是否是横切关注点?(Across)
│   ├── 是 → 使用Mixin
│   └── 否 → 继续检查
├── 是否是行为或能力的扩展?(Behavior)
│   ├── 是 → 使用Mixin
│   └── 否 → 继续检查
└── 是否涉及状态或资源管理?(Stateful)
    ├── 是 → 使用Mixin
    └── 否 → 考虑其他方案

让我们通过一个图片查看器的例子,按照决策树逐步分析和实现。

问题描述:需要实现一个图片查看器,要求:

  1. 支持缩放和拖动
  2. 可以双击放大
  3. 需要记录查看历史
  4. 支持加载状态显示
  5. 可以保存到本地

让我们按决策树逐步分析:

  1. 是否需要组合多个独立功能?(Composition)
  • 是:需要组合缩放、拖动、双击等手势功能
  1. 是否需要在多个地方复用代码?(Reusability)
  • 是:手势处理和加载状态可能在其他地方也需要
  1. 是否是横切关注点?(Across)
  • 是:加载状态和历史记录是横切功能
  1. 是否是行为或能力的扩展?(Behavior)
  • 是:手势处理是行为扩展
  1. 是否涉及状态或资源管理?(Stateful)
  • 是:需要管理图片加载状态和历史记录

根据分析,实现代码:

// 1. 手势处理 (Behavior)
mixin ImageGestureMixin {
  double _scale = 1.0;
  double _previousScale = 1.0;
  Offset _offset = Offset.zero;
  Offset _previousOffset = Offset.zero;
  
  void onScaleStart(ScaleStartDetails details) {
    _previousScale = _scale;
    _previousOffset = _offset;
  }
  
  void onScaleUpdate(ScaleUpdateDetails details) {
    _scale = (_previousScale * details.scale).clamp(0.5, 3.0);
    _offset = _previousOffset + details.focalPointDelta;
    onGestureUpdated();
  }
  
  void onDoubleTap() {
    _scale = _scale == 1.0 ? 2.0 : 1.0;
    _offset = _scale == 1.0 ? Offset.zero : _offset;
    onGestureUpdated();
  }
  
  // 由实现类提供
  void onGestureUpdated();
  
  Matrix4 get transform => Matrix4.identity()
    ..translate(_offset.dx, _offset.dy)
    ..scale(_scale);
}

// 2. 加载状态管理 (Across & Stateful)
mixin LoadingStateMixin {
  bool _isLoading = false;
  String? _error;
  
  bool get isLoading => _isLoading;
  String? get error => _error;
  
  Future<T> handleLoading<T>(Future<T> Function() operation) async {
    try {
      _isLoading = true;
      _error = null;
      onLoadingStateChanged();
      return await operation();
    } catch (e) {
      _error = e.toString();
      onLoadingStateChanged();
      rethrow;
    } finally {
      _isLoading = false;
      onLoadingStateChanged();
    }
  }
  
  // 由实现类提供
  void onLoadingStateChanged();
}

// 3. 历史记录管理 (Across & Stateful)
mixin ViewHistoryMixin {
  final _history = <String>[];
  
  void addToHistory(String imageUrl) {
    if (!_history.contains(imageUrl)) {
      _history.add(imageUrl);
      if (_history.length > 20) _history.removeAt(0);
      onHistoryUpdated();
    }
  }
  
  List<String> get viewHistory => List.unmodifiable(_history);
  
  // 由实现类提供
  void onHistoryUpdated();
}

// 实现图片查看器
class ImageViewer extends StatefulWidget {
  final String imageUrl;
  
  ImageViewer({required this.imageUrl});
  
  @override
  _ImageViewerState createState() => _ImageViewerState();
}

class _ImageViewerState extends State<ImageViewer> 
    with ImageGestureMixin, LoadingStateMixin, ViewHistoryMixin {
  late ImageProvider _imageProvider;
  
  @override
  void initState() {
    super.initState();
    _loadImage();
  }
  
  Future<void> _loadImage() async {
    await handleLoading(() async {
      _imageProvider = NetworkImage(widget.imageUrl);
      await precacheImage(_imageProvider, context);
      addToHistory(widget.imageUrl);
    });
  }
  
  @override
  void onGestureUpdated() {
    setState(() {});
  }
  
  @override
  void onLoadingStateChanged() {
    setState(() {});
  }
  
  @override
  void onHistoryUpdated() {
    // 可以选择是否需要更新UI
  }
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleStart: onScaleStart,
      onScaleUpdate: onScaleUpdate,
      onDoubleTap: onDoubleTap,
      child: Stack(
        children: [
          if (isLoading)
            const Center(child: CircularProgressIndicator()),
          if (error != null)
            Center(child: Text(error!)),
          if (!isLoading && error == null)
            Transform(
              transform: transform,
              child: Image(image: _imageProvider),
            ),
        ],
      ),
    );
  }
}

// 使用示例
void main() {
  runApp(MaterialApp(
    home: Scaffold(
      body: ImageViewer(
        imageUrl: 'https://example.com/image.jpg',
      ),
    ),
  ));
}

这个实现:

  1. 分离了不同的关注点
  2. 每个mixin负责单一功能
  3. 功能可以独立复用
  4. 代码组织清晰
  5. 状态管理明确

这样的设计让代码:

  • 易于维护:每个功能都是独立的
  • 易于测试:可以单独测试每个mixin
  • 易于扩展:可以方便地添加新功能
  • 易于复用:每个mixin都可以在其他地方使用

如果之后需要添加新功能(如保存图片),只需要添加新的mixin,不会影响现有代码。