痛点场景:绘图应用的操作管理

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87380.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87380.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/87380.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI大模型应用开发完整学习体系

&#x1f3af; AI大模型应用开发完整学习体系 第一部分&#xff1a;课程核心内容 本课程系统化构建AI大模型应用开发能力体系&#xff0c;涵盖五大核心模块&#xff1a; 1️⃣ AI大模型开发基础 深入理解大模型架构&#xff08;如DeepSeek&#xff09;、Prompt工程优化、Cu…

UG NX二次开发(C#)-读取PMI对象的名称

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、在UG NX中设置PMI对象名称3、采用NXOpen获取PMI对象名称1、前言 PMI对象是UG NX的一个很重要的对象,其获取主要是通过NXOpen来实现,在QQ群有群友问下如何获取PMI的对象名称,我们这篇…

大数据时代UI前端的智能决策支持:基于数据驱动的产品优化

hello宝子们...我们是艾斯视觉擅长ui设计、前端开发、数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 一、引言&#xff1a;数据驱动决策的前端智能化变革 在数字化转型的浪潮中&#xff0c;UI 前…

服务器性能调优实战:如何在高负载下维持系统稳定性?

更多云服务器知识&#xff0c;尽在hostol.com 当服务器遭遇高负载时&#xff0c;它就像一个拼命运转的发动机&#xff0c;任何小小的波动都可能导致系统崩溃。你也许会看到 CPU 突然飙升、内存紧张、响应延迟增加&#xff0c;甚至进程挂掉。而这一切往往发生得悄无声息&#x…

CSS `@scope` 实战指南:开启局部样式隔离新时代

&#x1f9ec; CSS scope 实战指南&#xff1a;开启局部样式隔离新时代 你是否曾担心组件样式被全局覆盖&#xff1f;是否为命名空间冲突而头痛&#xff1f;CSS scope 是原生支持的作用域样式机制&#xff0c;让你不再依赖 BEM、CSS Modules、Scoped CSS 等方案&#xff0c;也能…

spring-ai-alibaba 1.0.0.2 学习(六)——DocumentReader与DocumentParser

spring-ai-alibaba提供了许多读取外部文档的包&#xff0c;例如语雀、飞书、notion笔记等 这些包以spring-ai-alibaba-starter-document-reader开头&#xff0c;实现了spring-ai的DocumentReader接口 最简单样例 我们一起来看一个最简单的例子&#xff0c;以spring-ai-aliba…

在银河麒麟V10 SP1上手动安装与配置高版本Docker的完整指南

原文链接&#xff1a;在银河麒麟V10 SP1上手动安装与配置高版本Docker的完整指南 Hello&#xff0c;大家好啊&#xff0c;今天给大家带来一篇银河麒麟桌面操作系统&#xff08;Kylin V10 SP1&#xff09;上安装与配置Docker的文章&#xff0c;详细介绍从下载安装到运行容器的每…

如何在电脑上完全抹去历史记录

要在电脑上‌完全抹去历史记录‌&#xff08;包括浏览记录、文件痕迹、系统日志等&#xff09;&#xff0c;需根据需求选择不同级别的清理方案。以下是分步骤的彻底清理指南&#xff1a; ‌一、基础清理&#xff1a;删除常见痕迹‌ ‌1. 浏览器记录清除‌ ‌Chrome/Firefox/E…

大数据环境搭建指南:基于 Docker 构建 Hadoop、Hive、HBase 等服务

大数据环境搭建指南&#xff1a;基于 Docker 构建 Hadoop、Hive、HBase 等服务 说明大数据环境搭建指南&#xff1a;基于 Docker 构建 Hadoop、Hive、HBase 等服务一、引言二、项目概述三、搭建步骤3.1 下载文件3.2 构建镜像3.2.1 构建基础层镜像3.2.2 并行构建 HBase/Hive/Spa…

AWS WebRTC:根据viewer端拉流日志推算视频帧率和音频帧率

viewer端拉流日志是这样的&#xff1a; 07:19:26.263 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 140092278368896, Size: 160, Flags 3210729368 2025-06-12 07:19:26.283 VERBOSE sampleAudioFrameHandler(): Audio Frame received. TrackId: 14009…

Vue.js——组件基础

目录 选项式API和组合式API 选项式API 组合式API 语法糖 选项式API和组合式API的关系 生命周期函数 组合式API的生命周期函数 选项式API的生命周期函数 组件的注册和引用 注册组件 全局注册 局部注册 引用组件 解决组件之间的样式冲突 scoped属性 深度选择器 …

Yii2 安装-yii2-imagine

#composer 安装-如已安装跳过 php -r "copy(https://install.phpcomposer.com/installer, composer-setup.php);" php composer-setup.php sudo mv composer.phar /usr/local/bin/composer#执行安装 composer require --prefer-dist yiisoft/yii2-imagine#报错 Updat…

C#程序设计简介

一、发展历史 C#的主要作者是丹麦计算机科学家安德斯海尔斯伯格&#xff08;Anders Hejlsberg&#xff09;&#xff0c;他是该语言的首席设计师&#xff0c;同时也是Turbo Pascal&#xff08;Pascal 语言编译器&#xff09;、Delphi&#xff08;由 Borland&#xff08;后被 Em…

JavaWeb笔记03

七、Maven1_概述Maven 是专门用于管理和构建 Java 项目的工具&#xff0c;它的主要功能有: 提供了一套标准化的项目结构 提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布……&#xff09; 提供了一套依赖管理机制1.标准化的项目结…

AIGC自我介绍笔记

AIGC&#xff08;人工智能生成内容&#xff09;项目是指利用人工智能技术&#xff08;如深度学习、生成对抗网络、大规模预训练模型等&#xff09;自动生成文本、图像、音频、视频等多模态内容的系统性工程。这类项目通过算法模型学习海量数据&#xff0c;实现内容的自动化、个…

从docker-compose快速入门Docker

不得不提容器化技术是未来的一个发展方向&#xff0c;它彻底释放了计算虚拟化的威力&#xff0c;极大提高了应用的运行效率&#xff0c;降低了云计算资源供应的成本&#xff01;使用 Docker&#xff0c;可以让应用的部署、测试和分发都变得前所未有的高效和轻松&#xff01;无论…

【BERT_Pretrain】Wikipedia_Bookcorpus数据预处理(二)

上一篇介绍了wikipedia和bookcopus数据集&#xff0c;这一篇主要讲一下如何预处理数据&#xff0c;使其可以用于BERT的Pretrain任务MLM和NSP。 MLM是类似于完形填空的任务&#xff0c;NSP是判断两个句子是否连着。因此数据预处理的方式不同。首先&#xff0c;拿到原始数据集&a…

人工智能-基础篇-14-知识库和知识图谱介绍(知识库是基石、知识图谱是增强语义理解的知识库、结构化数据和非结构化数据区分)

在人工智能&#xff08;AI&#xff09;领域&#xff0c;知识图谱&#xff08;Knowledge Graph&#xff09;和知识库&#xff08;Knowledge Base&#xff09;是两种重要的知识表示和管理技术&#xff0c;它们的核心目标是通过结构化的方式组织信息&#xff0c;从而支持智能系统的…

7月1日作业

思维导图 一、将当前的时间写入到time.txt的文件中&#xff0c;如果ctrlc退出之后&#xff0c;在再次执行支持断点续写 1.2022-04-26 19:10:20 2.2022-04-26 19:10:21 3.2022-04-26 19:10:22 //按下ctrlc停止&#xff0c;再次执行程序 4.2022-04-26 20:00:00 5.2022-04-26 20:0…

DHCP中继及动态分配

DHCP中继 在多 VLAN 网络中为什么不能直接用 DHCP&#xff1f; 比如你现在的网络是&#xff1a;PC 在 VLAN10、VLAN20 中DHCP服务器&#xff08;Router0&#xff09;在另一个网段&#xff08;比如 192.168.100.0/24&#xff09;PC 的 DHCP Discover 是广播&#xff0c;无法跨越…