可以总结为以下原则,我称之为"CRABS原则"(Composition复合, Reusability复用, Across横切, Behavior行为, Stateful状态):
-
当需要在多个类中复用代码,但不适合用继承时(Composition原则)
- 这些类可能已经有了父类
- 需要组合多个功能
- 功能是可选的,不是类的核心特性
-
当代码模式在项目中重复出现时(Reusability原则)
- 表单验证逻辑
- 加载状态管理
- 错误处理模式
- 资源释放模式
-
当功能是横切关注点时(Across原则)
- 日志记录
- 性能监控
- 权限检查
- 缓存管理
- 主题适配
-
当需要为类添加能力或行为时(Behavior原则)
- 手势处理
- 动画控制
- 生命周期行为
- 网络状态感知
-
当需要管理状态或资源时(Stateful原则)
- 控制器管理
- 订阅管理
- 缓存状态
- 加载状态
判断是否使用Mixin的快速检查表:
// 如果你的代码符合以下情况,就应该考虑使用Mixin:
✓ 这段代码会在多个不相关的类中使用
✓ 这个功能是可选的,不是核心功能
✓ 这段代码不依赖于类的具体实现
✓ 这个功能需要和其他功能组合
✓ 这段代码是一个横切关注点
✓ 这个功能是一种能力或行为的扩展
✓ 这段代码管理着某种状态或资源
// 相反,如果符合以下情况,可能不适合使用Mixin:
✗ 功能是类的核心特性
✗ 代码强依赖于类的具体实现
✗ 功能之间有强耦合关系
✗ 只在单个类中使用
✗ 需要维护复杂的状态
决策树
是否需要Mixin?
├── 是否需要组合多个独立功能?(Composition)
│ ├── 是 → 使用Mixin
│ └── 否 → 继续检查
├── 是否需要在多个地方复用代码?(Reusability)
│ ├── 是 → 使用Mixin
│ └── 否 → 继续检查
├── 是否是横切关注点?(Across)
│ ├── 是 → 使用Mixin
│ └── 否 → 继续检查
├── 是否是行为或能力的扩展?(Behavior)
│ ├── 是 → 使用Mixin
│ └── 否 → 继续检查
└── 是否涉及状态或资源管理?(Stateful)
├── 是 → 使用Mixin
└── 否 → 考虑其他方案
让我们通过一个图片查看器的例子,按照决策树逐步分析和实现。
问题描述:需要实现一个图片查看器,要求:
- 支持缩放和拖动
- 可以双击放大
- 需要记录查看历史
- 支持加载状态显示
- 可以保存到本地
让我们按决策树逐步分析:
- 是否需要组合多个独立功能?(Composition)
- 是:需要组合缩放、拖动、双击等手势功能
- 是否需要在多个地方复用代码?(Reusability)
- 是:手势处理和加载状态可能在其他地方也需要
- 是否是横切关注点?(Across)
- 是:加载状态和历史记录是横切功能
- 是否是行为或能力的扩展?(Behavior)
- 是:手势处理是行为扩展
- 是否涉及状态或资源管理?(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',
),
),
));
}
这个实现:
- 分离了不同的关注点
- 每个mixin负责单一功能
- 功能可以独立复用
- 代码组织清晰
- 状态管理明确
这样的设计让代码:
- 易于维护:每个功能都是独立的
- 易于测试:可以单独测试每个mixin
- 易于扩展:可以方便地添加新功能
- 易于复用:每个mixin都可以在其他地方使用
如果之后需要添加新功能(如保存图片),只需要添加新的mixin,不会影响现有代码。