痛点场景:绘图应用的操作管理
假设你在开发一个绘图App,需要支持:
- 添加/删除图形
- 修改图形属性
- 撤销/重做操作
- 批量执行命令
传统实现方式:
void _handleAddShape(ShapeType type) {
final shape = _createShape(type);
setState(() => _shapes.add(shape));
}
void _handleDeleteShape(Shape shape) {
setState(() => _shapes.remove(shape));
}
void _handleChangeColor(Shape shape, Color newColor) {
setState(() => shape.color = newColor);
}
// 撤销操作?需要自己维护状态...
问题爆发点:
- 🔄 撤销/重做功能难以实现
- 📦 批量操作无法封装
- 🔗 操作与执行代码紧密耦合
- ⏱️ 延迟执行或排队操作困难
命令模式解决方案
核心思想: 将请求封装为对象,从而允许:
- 参数化客户端不同请求
- 排队或记录请求
- 支持可撤销操作
四个关键角色:
- 命令接口(Command): 声明执行操作
- 具体命令(ConcreteCommand): 绑定接收者与动作
- 接收者(Receiver): 知道如何执行操作
- 调用者(Invoker): 触发命令执行
Flutter绘图命令实现
1. 定义命令接口
abstract class DrawingCommand {
void execute();
void undo();
String get description; // 用于命令历史显示
}
2. 实现具体命令
// 添加图形命令
class AddShapeCommand implements DrawingCommand {
final List<Shape> _shapes;
final Shape _shape;
String get description => '添加 ${_shape.type}';
AddShapeCommand(this._shapes, this._shape);
void execute() => _shapes.add(_shape);
void undo() => _shapes.remove(_shape);
}
// 修改颜色命令
class ChangeColorCommand implements DrawingCommand {
final Shape _shape;
final Color _newColor;
Color _previousColor;
String get description => '修改颜色';
ChangeColorCommand(this._shape, this._newColor);
void execute() {
_previousColor = _shape.color;
_shape.color = _newColor;
}
void undo() {
_shape.color = _previousColor;
}
}
// 删除图形命令
class DeleteShapeCommand implements DrawingCommand {
final List<Shape> _shapes;
final Shape _shape;
int _index = -1;
String get description => '删除 ${_shape.type}';
DeleteShapeCommand(this._shapes, this._shape);
void execute() {
_index = _shapes.indexOf(_shape);
_shapes.remove(_shape);
}
void undo() {
if (_index != -1) {
_shapes.insert(_index, _shape);
}
}
}
3. 创建命令管理器
class CommandManager {
final List<DrawingCommand> _commandHistory = [];
final List<DrawingCommand> _undoStack = [];
void executeCommand(DrawingCommand command) {
command.execute();
_commandHistory.add(command);
_undoStack.clear();
notifyListeners();
}
void undo() {
if (_commandHistory.isEmpty) return;
final command = _commandHistory.removeLast();
command.undo();
_undoStack.add(command);
notifyListeners();
}
void redo() {
if (_undoStack.isEmpty) return;
final command = _undoStack.removeLast();
command.execute();
_commandHistory.add(command);
notifyListeners();
}
bool get canUndo => _commandHistory.isNotEmpty;
bool get canRedo => _undoStack.isNotEmpty;
// 与ChangeNotifier结合
final _changeNotifier = ChangeNotifier();
void addListener(VoidCallback listener) => _changeNotifier.addListener(listener);
void removeListener(VoidCallback listener) => _changeNotifier.removeListener(listener);
void notifyListeners() => _changeNotifier.notifyListeners();
}
4. 在Flutter中使用
class DrawingApp extends StatefulWidget {
_DrawingAppState createState() => _DrawingAppState();
}
class _DrawingAppState extends State<DrawingApp> {
final List<Shape> _shapes = [];
final CommandManager _commandManager = CommandManager();
void initState() {
super.initState();
_commandManager.addListener(_refresh);
}
void _refresh() => setState(() {});
void _addCircle() {
_commandManager.executeCommand(
AddShapeCommand(_shapes, Circle(Colors.blue)),
);
}
void _changeColor(Shape shape) {
_commandManager.executeCommand(
ChangeColorCommand(shape, Colors.red),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('绘图应用'),
actions: [
IconButton(
icon: Icon(Icons.undo),
onPressed: _commandManager.canUndo
? _commandManager.undo
: null,
),
IconButton(
icon: Icon(Icons.redo),
onPressed: _commandManager.canRedo
? _commandManager.redo
: null,
),
],
),
body: Stack(
children: [
..._shapes.map((shape) => DraggableShape(
shape: shape,
onColorChange: () => _changeColor(shape),
)),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _addCircle,
child: Icon(Icons.add),
),
);
}
}
Flutter中的实际应用场景
场景1:宏命令(批量操作)
class MacroCommand implements DrawingCommand {
final List<DrawingCommand> _commands = [];
void addCommand(DrawingCommand command) {
_commands.add(command);
}
String get description => '批量操作 (${_commands.length}个命令)';
void execute() {
for (final command in _commands) {
command.execute();
}
}
void undo() {
for (var i = _commands.length - 1; i >= 0; i--) {
_commands[i].undo();
}
}
}
// 使用
final macro = MacroCommand()
..addCommand(AddShapeCommand(_shapes, Circle(Colors.red)))
..addCommand(AddShapeCommand(_shapes, Rectangle(Colors.blue)))
..addCommand(ChangeColorCommand(_shapes[0], Colors.green));
_commandManager.executeCommand(macro);
场景2:事务操作
class TransactionCommand implements DrawingCommand {
final List<DrawingCommand> _commands = [];
bool _executed = false;
void addCommand(DrawingCommand command) {
if (_executed) throw StateError('事务已执行');
_commands.add(command);
}
String get description => '事务操作';
void execute() {
try {
for (final command in _commands) {
command.execute();
}
_executed = true;
} catch (e) {
// 任何一个命令失败就回滚
for (var i = _commands.length - 1; i >= 0; i--) {
_commands[i].undo();
}
rethrow;
}
}
void undo() {
if (!_executed) return;
for (var i = _commands.length - 1; i >= 0; i--) {
_commands[i].undo();
}
_executed = false;
}
}
场景3:远程控制(跨平台命令)
abstract class RemoteCommand {
Future<void> execute();
Map<String, dynamic> toJson();
factory RemoteCommand.fromJson(Map<String, dynamic> json) {
// 根据json创建具体命令
}
}
class SaveDrawingCommand implements RemoteCommand {
final List<Shape> shapes;
Future<void> execute() async {
await Api.saveDrawing(shapes);
}
Map<String, dynamic> toJson() => {
'type': 'save',
'shapes': shapes.map((s) => s.toJson()).toList(),
};
}
// 通过平台通道执行
MethodChannel('commands').setMethodCallHandler((call) async {
final command = RemoteCommand.fromJson(call.arguments);
await command.execute();
});
命令模式与状态管理结合
将命令历史与Provider结合:
class CommandHistoryProvider extends ChangeNotifier {
final CommandManager _manager;
CommandHistoryProvider(this._manager) {
_manager.addListener(notifyListeners);
}
List<String> get commandHistory =>
_manager._commandHistory.map((c) => c.description).toList();
List<String> get undoStack =>
_manager._undoStack.map((c) => c.description).toList();
}
// 在UI中显示历史
Consumer<CommandHistoryProvider>(
builder: (context, provider, child) {
return Column(
children: [
Text('操作历史:'),
...provider.commandHistory.map((desc) => Text(desc)),
SizedBox(height: 20),
Text('可重做操作:'),
...provider.undoStack.map((desc) => Text(desc)),
],
);
}
)
命令模式最佳实践
何时使用命令模式:
- 需要实现撤销/重做功能
- 需要支持事务操作
- 需要将操作排队或延迟执行
- 需要支持宏命令(命令组合)
- 需要支持跨平台操作
Flutter特化技巧:
// 将命令与Shortcuts绑定 Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.z): const UndoIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.y): const RedoIntent(), }, child: Actions( actions: { UndoIntent: CallbackAction( onInvoke: (_) => _commandManager.undo(), ), RedoIntent: CallbackAction( onInvoke: (_) => _commandManager.redo(), ), }, child: Builder(builder: (context) => ...), ), )
性能优化:
// 懒执行命令 class LazyCommand implements DrawingCommand { final Future<void> Function() _action; bool _executed = false; void execute() async { if (!_executed) { await _action(); _executed = true; } } }
测试策略:
test('撤销应恢复原始状态', () { final shapes = [Circle(Colors.red)]; final command = ChangeColorCommand(shapes[0], Colors.blue); command.execute(); expect(shapes[0].color, Colors.blue); command.undo(); expect(shapes[0].color, Colors.red); });
命令模式 vs 策略模式
特性 | 命令模式 | 策略模式 |
---|---|---|
目的 | 封装操作请求 | 封装算法 |
关注点 | 何时/如何执行操作 | 如何完成特定任务 |
典型应用 | 撤销/重做/事务 | 算法替换/策略切换 |
执行时机 | 可延迟/排队执行 | 通常立即执行 |
命令模式的高级变体
1. 可逆命令工厂
class CommandFactory {
static DrawingCommand createAddCommand(List<Shape> shapes, ShapeType type) {
return AddShapeCommand(shapes, _createShape(type));
}
static DrawingCommand createDeleteCommand(List<Shape> shapes, Shape shape) {
return DeleteShapeCommand(shapes, shape);
}
// 注册自定义命令
static void register(String type, DrawingCommand Function() creator) {
_customCommands[type] = creator;
}
}
2. 命令持久化
class PersistentCommand implements DrawingCommand {
final SharedPreferences _prefs;
final String _key;
void execute() async {
await _prefs.setString(_key, 'executed');
}
void undo() async {
await _prefs.remove(_key);
}
Future<bool> get isExecuted async {
return _prefs.containsKey(_key);
}
}
3. 时间旅行调试
class TimeTravelManager {
final List<List<DrawingCommand>> _timeline = [];
int _currentState = -1;
void snapshot(List<DrawingCommand> commands) {
// 移除当前状态之后的所有状态
if (_currentState < _timeline.length - 1) {
_timeline.removeRange(_currentState + 1, _timeline.length);
}
_timeline.add(List.from(commands));
_currentState = _timeline.length - 1;
}
void goToState(int index) {
if (index >= 0 && index < _timeline.length) {
_currentState = index;
// 重新执行到目标状态的所有命令
}
}
}
总结:命令模式是你的操作保险箱
- 核心价值: 将操作封装为对象,实现操作管理的高级功能
- Flutter优势:
- 实现撤销/重做功能
- 支持事务和批量操作
- 解耦操作发起者和执行者
- 与Flutter快捷键系统完美结合
- 适用场景: 绘图应用、文本编辑器、事务系统、操作历史记录
⏳ 设计启示: 当你需要控制操作的"时间维度"(撤销/重做)或"空间维度"(跨平台执行)时,命令模式就是你的"时间机器"!