命令模式 - Flutter中的操作封装大师,把“动作“变成可管理的对象!

发布于:2025-06-30 ⋅ 阅读:(12) ⋅ 点赞:(0)

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到