命令模式 - 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快捷鍵系統完美結合
  • 適用場景: 繪圖應用、文本編輯器、事務系統、操作歷史記錄

? 設計啟示: 當你需要控制操作的"時間維度"(撤銷/重做)或"空間維度"(跨平臺執行)時,命令模式就是你的"時間機器"!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處: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;無法跨越…