当前位置: 首页 > news >正文

命令模式 - 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);
}// 撤销操作?需要自己维护状态...

问题爆发点:

  • 🔄 撤销/重做功能难以实现
  • 📦 批量操作无法封装
  • 🔗 操作与执行代码紧密耦合
  • ⏱️ 延迟执行或排队操作困难

命令模式解决方案

核心思想: 将请求封装为对象,从而允许:

  • 参数化客户端不同请求
  • 排队或记录请求
  • 支持可撤销操作

四个关键角色:

  1. 命令接口(Command): 声明执行操作
  2. 具体命令(ConcreteCommand): 绑定接收者与动作
  3. 接收者(Receiver): 知道如何执行操作
  4. 调用者(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)),],);}
)

命令模式最佳实践

  1. 何时使用命令模式:

    • 需要实现撤销/重做功能
    • 需要支持事务操作
    • 需要将操作排队或延迟执行
    • 需要支持宏命令(命令组合)
    • 需要支持跨平台操作
  2. 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) => ...),),
    )
    
  3. 性能优化:

    // 懒执行命令
    class LazyCommand implements DrawingCommand {final Future<void> Function() _action;bool _executed = false;void execute() async {if (!_executed) {await _action();_executed = true;}}
    }
    
  4. 测试策略:

    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快捷键系统完美结合
  • 适用场景: 绘图应用、文本编辑器、事务系统、操作历史记录

设计启示: 当你需要控制操作的"时间维度"(撤销/重做)或"空间维度"(跨平台执行)时,命令模式就是你的"时间机器"!

相关文章:

  • 数据同步工具对比:Canal、DataX与Flink CDC
  • stm32hal模块驱动(2)bmi270气压计
  • 数据结构之单链表
  • 爬虫实战之图片及人物信息爬取
  • 华为云Flexus+DeepSeek征文 | 华为云 ModelArts Studio 赋能 AI 法务:合同审查与法律文件生成系统
  • 【硬核数学】4. AI的“寻路”艺术:优化理论如何找到模型的最优解《从零构建机器学习、深度学习到LLM的数学认知》
  • Leetcode 3598. Longest Common Prefix Between Adjacent Strings After Removals
  • 滑块验证码(1)
  • 【blender】使用bpy对一个obj的不同mesh进行不同的材质贴图(涉及对bmesh的操作)
  • ViTMatte:利用预训练的基础视觉Transformer提升图像抠图性能
  • 云计算在布莱克-斯科尔斯模型中的应用:解析解、蒙特卡洛模拟与可视化-AI云计算数值分析和代码验证
  • Node.js特训专栏-实战进阶:11. Redis缓存策略与应用场景
  • 【更新至2024年】1999-2024年各省城镇居民人均消费支出数据(无缺失)
  • 八股文——JAVA基础:String s1 = new String(“abc“);这句话创建了几个字符串对象?
  • window11 本地安装 MySQL8.0
  • SAP顾问职位汇总(第26周)
  • 数据分析标普500
  • 实现win系统控制局域网的linux主机桌面
  • 现代 JavaScript (ES6+) 入门到实战(三):字符串与对象的魔法升级—模板字符串/结构赋值/展开运算符
  • 知攻善防靶机 Windows 挖矿事件应急