命令模式 - Flutter中的操作封装大师,把“动作“变成可管理的对象!
痛点场景:绘图应用的操作管理
假设你在开发一个绘图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快捷键系统完美结合
- 适用场景: 绘图应用、文本编辑器、事务系统、操作历史记录
⏳ 设计启示: 当你需要控制操作的"时间维度"(撤销/重做)或"空间维度"(跨平台执行)时,命令模式就是你的"时间机器"!