【设计模式手册013】命令模式 - 请求封装的优雅之道

设计模式手册013:命令模式 - 请求封装的优雅之道

本文是「设计模式手册」系列第013篇,我们将深入探讨命令模式,这种模式将请求封装为对象,从而让你可以用不同的请求、队列或日志来参数化其他对象,同时支持可撤销的操作。

1. 场景:我们为何需要命令模式?

在软件开发中,我们经常遇到需要将请求发送者与接收者解耦的场景:

  • 图形界面操作:按钮点击触发复杂业务逻辑
  • 任务队列:将操作封装为任务,支持延迟执行和撤销
  • 事务系统:支持操作的事务性和回滚
  • 宏命令:将多个操作组合成一个原子操作
  • 远程调用:将本地调用封装为可网络传输的命令对象

传统做法的困境

java 复制代码
// 紧耦合的设计 - 按钮直接调用业务逻辑
public class Button {
    private String label;
    
    public Button(String label) {
        this.label = label;
    }
    
    // 问题:按钮直接依赖具体的业务逻辑
    public void onClick() {
        if ("保存".equals(label)) {
            new Document().save();
        } else if ("打开".equals(label)) {
            new Document().open();
        } else if ("打印".equals(label)) {
            new Document().print();
        }
        // 每增加一个功能,就要修改Button类
    }
}

public class Document {
    public void save() {
        System.out.println("保存文档...");
    }
    
    public void open() {
        System.out.println("打开文档...");
    }
    
    public void print() {
        System.out.println("打印文档...");
    }
}

// 菜单类也有同样的问题
public class Menu {
    public void selectMenuItem(String itemName) {
        if ("保存".equals(itemName)) {
            new Document().save();
        } else if ("打开".equals(itemName)) {
            new Document().open();
        }
        // 重复的判断逻辑
    }
}

// 使用示例
public class TightCouplingExample {
    public static void main(String[] args) {
        Button saveButton = new Button("保存");
        Button openButton = new Button("打开");
        
        saveButton.onClick(); // 输出:保存文档...
        openButton.onClick(); // 输出:打开文档...
    }
}

这种实现的痛点

  • 紧耦合:UI组件直接依赖业务逻辑
  • 违反开闭原则:新增功能需要修改现有代码
  • 代码重复:相同的判断逻辑在不同UI组件中重复
  • 难以扩展:无法支持撤销、重做、宏命令等高级功能

2. 命令模式:定义与本质

2.1 模式定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

2.2 核心角色

java 复制代码
// 命令接口:声明执行操作的方法
public interface Command {
    void execute();
    void undo(); // 支持撤销操作
}

// 具体命令:实现命令接口,将一个接收者对象绑定于一个动作
public class SaveCommand implements Command {
    private Document document;
    private String previousContent; // 用于撤销操作
    
    public SaveCommand(Document document) {
        this.document = document;
    }
    
    @Override
    public void execute() {
        // 保存当前状态以便撤销
        this.previousContent = document.getContent();
        document.save();
    }
    
    @Override
    public void undo() {
        if (previousContent != null) {
            document.setContent(previousContent);
            System.out.println("撤销保存操作,恢复内容为: " + previousContent);
        }
    }
}

public class OpenCommand implements Command {
    private Document document;
    
    public OpenCommand(Document document) {
        this.document = document;
    }
    
    @Override
    public void execute() {
        document.open();
    }
    
    @Override
    public void undo() {
        System.out.println("无法撤销打开操作");
    }
}

public class PrintCommand implements Command {
    private Document document;
    
    public PrintCommand(Document document) {
        this.document = document;
    }
    
    @Override
    public void execute() {
        document.print();
    }
    
    @Override
    public void undo() {
        System.out.println("无法撤销打印操作");
    }
}

// 接收者:知道如何实施与执行一个请求相关的操作
public class Document {
    private String content;
    private String filename;
    
    public Document() {
        this.content = "";
        this.filename = "未命名文档";
    }
    
    public void save() {
        System.out.println("保存文档: " + filename);
        // 实际的保存逻辑
    }
    
    public void open() {
        System.out.println("打开文档: " + filename);
        // 实际的打开逻辑
    }
    
    public void print() {
        System.out.println("打印文档: " + filename);
        System.out.println("内容: " + content);
        // 实际的打印逻辑
    }
    
    // getter和setter
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }
}

// 调用者:要求该命令执行这个请求
public class Button {
    private String label;
    private Command command;
    
    public Button(String label, Command command) {
        this.label = label;
        this.command = command;
    }
    
    public void onClick() {
        System.out.print("按钮[" + label + "]被点击 - ");
        command.execute();
    }
    
    // 支持动态改变命令
    public void setCommand(Command command) {
        this.command = command;
    }
}

// 使用命令模式
public class CommandPatternDemo {
    public static void main(String[] args) {
        // 创建接收者
        Document document = new Document();
        document.setContent("这是一个重要的文档内容");
        document.setFilename("报告.docx");
        
        // 创建具体命令
        Command saveCommand = new SaveCommand(document);
        Command openCommand = new OpenCommand(document);
        Command printCommand = new PrintCommand(document);
        
        // 创建调用者并绑定命令
        Button saveButton = new Button("保存", saveCommand);
        Button openButton = new Button("打开", openCommand);
        Button printButton = new Button("打印", printCommand);
        
        // 执行命令
        saveButton.onClick(); // 输出:按钮[保存]被点击 - 保存文档: 报告.docx
        openButton.onClick(); // 输出:按钮[打开]被点击 - 打开文档: 报告.docx
        printButton.onClick(); // 输出:按钮[打印]被点击 - 打印文档: 报告.docx
        
        // 测试撤销
        saveCommand.undo(); // 输出:撤销保存操作,恢复内容为: 这是一个重要的文档内容
    }
}

3. 深入理解:命令模式的多维视角

3.1 第一重:简单命令 vs 复杂命令

简单命令
java 复制代码
// 简单命令:直接执行单一操作
public class SimpleCommand implements Command {
    private Runnable action;
    
    public SimpleCommand(Runnable action) {
        this.action = action;
    }
    
    @Override
    public void execute() {
        action.run();
    }
    
    @Override
    public void undo() {
        System.out.println("简单命令不支持撤销");
    }
}

// 使用函数式接口创建命令
public class FunctionalCommand implements Command {
    private final Runnable executeAction;
    private final Runnable undoAction;
    
    public FunctionalCommand(Runnable executeAction, Runnable undoAction) {
        this.executeAction = executeAction;
        this.undoAction = undoAction;
    }
    
    @Override
    public void execute() {
        executeAction.run();
    }
    
    @Override
    public void undo() {
        if (undoAction != null) {
            undoAction.run();
        }
    }
}
复杂命令(宏命令)
java 复制代码
// 宏命令:包含多个命令的复合命令
public class MacroCommand implements Command {
    private List<Command> commands = new ArrayList<>();
    private List<Command> executedCommands = new ArrayList<>();
    
    public void addCommand(Command command) {
        commands.add(command);
    }
    
    public void removeCommand(Command command) {
        commands.remove(command);
    }
    
    @Override
    public void execute() {
        executedCommands.clear();
        for (Command command : commands) {
            command.execute();
            executedCommands.add(command);
        }
    }
    
    @Override
    public void undo() {
        // 按相反顺序撤销所有已执行的命令
        for (int i = executedCommands.size() - 1; i >= 0; i--) {
            executedCommands.get(i).undo();
        }
        executedCommands.clear();
    }
}

3.2 第二重:命令的历史管理

java 复制代码
// 命令历史管理器
public class CommandHistory {
    private Stack<Command> history = new Stack<>();
    private Stack<Command> redoStack = new Stack<>();
    
    public void execute(Command command) {
        command.execute();
        history.push(command);
        redoStack.clear(); // 执行新命令时清空重做栈
    }
    
    public void undo() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
            redoStack.push(command);
        } else {
            System.out.println("没有可以撤销的操作");
        }
    }
    
    public void redo() {
        if (!redoStack.isEmpty()) {
            Command command = redoStack.pop();
            command.execute();
            history.push(command);
        } else {
            System.out.println("没有可以重做的操作");
        }
    }
    
    public void clear() {
        history.clear();
        redoStack.clear();
    }
    
    public int getHistorySize() {
        return history.size();
    }
    
    public int getRedoSize() {
        return redoStack.size();
    }
}

3.3 第三重:参数化命令

java 复制代码
// 带参数的命令
public class ParameterizedCommand implements Command {
    private Document document;
    private String newContent;
    private String previousContent;
    
    public ParameterizedCommand(Document document, String newContent) {
        this.document = document;
        this.newContent = newContent;
    }
    
    @Override
    public void execute() {
        this.previousContent = document.getContent();
        document.setContent(newContent);
        System.out.println("文档内容已更新为: " + newContent);
    }
    
    @Override
    public void undo() {
        document.setContent(previousContent);
        System.out.println("撤销内容修改,恢复为: " + previousContent);
    }
}

// 支持动态参数的命令
public class DynamicCommand implements Command {
    private Consumer<Object[]> executeFunction;
    private Consumer<Object[]> undoFunction;
    private Object[] executeParams;
    private Object[] undoParams;
    
    public DynamicCommand(Consumer<Object[]> executeFunction, 
                         Consumer<Object[]> undoFunction,
                         Object[] executeParams, 
                         Object[] undoParams) {
        this.executeFunction = executeFunction;
        this.undoFunction = undoFunction;
        this.executeParams = executeParams;
        this.undoParams = undoParams;
    }
    
    @Override
    public void execute() {
        executeFunction.accept(executeParams);
    }
    
    @Override
    public void undo() {
        undoFunction.accept(undoParams);
    }
}

4. 实战案例:完整的文本编辑器

java 复制代码
// 文本编辑器 - 接收者
public class TextEditor {
    private String content = "";
    private int cursorPosition = 0;
    private String clipboard = "";
    
    public void insertText(String text) {
        String before = content.substring(0, cursorPosition);
        String after = content.substring(cursorPosition);
        content = before + text + after;
        cursorPosition += text.length();
        System.out.println("插入文本: \"" + text + "\",当前内容: \"" + content + "\"");
    }
    
    public void deleteText(int length) {
        if (cursorPosition >= length) {
            String deleted = content.substring(cursorPosition - length, cursorPosition);
            content = content.substring(0, cursorPosition - length) + 
                     content.substring(cursorPosition);
            cursorPosition -= length;
            System.out.println("删除文本: \"" + deleted + "\",当前内容: \"" + content + "\"");
        }
    }
    
    public void copyText(int start, int end) {
        if (start >= 0 && end <= content.length() && start < end) {
            clipboard = content.substring(start, end);
            System.out.println("复制文本: \"" + clipboard + "\"");
        }
    }
    
    public void pasteText() {
        if (!clipboard.isEmpty()) {
            insertText(clipboard);
        }
    }
    
    public void setCursorPosition(int position) {
        if (position >= 0 && position <= content.length()) {
            cursorPosition = position;
            System.out.println("设置光标位置: " + position);
        }
    }
    
    public void boldText(int start, int end) {
        if (start >= 0 && end <= content.length() && start < end) {
            String selected = content.substring(start, end);
            content = content.substring(0, start) + 
                     "**" + selected + "**" + 
                     content.substring(end);
            System.out.println("加粗文本: \"" + selected + "\",当前内容: \"" + content + "\"");
        }
    }
    
    // getter方法
    public String getContent() { return content; }
    public int getCursorPosition() { return cursorPosition; }
    public String getClipboard() { return clipboard; }
    
    // 用于命令模式的状态保存
    public EditorState saveState() {
        return new EditorState(content, cursorPosition, clipboard);
    }
    
    public void restoreState(EditorState state) {
        this.content = state.getContent();
        this.cursorPosition = state.getCursorPosition();
        this.clipboard = state.getClipboard();
        System.out.println("恢复编辑器状态");
    }
}

// 编辑器状态(用于撤销操作)
@Data
public class EditorState {
    private final String content;
    private final int cursorPosition;
    private final String clipboard;
    
    public EditorState(String content, int cursorPosition, String clipboard) {
        this.content = content;
        this.cursorPosition = cursorPosition;
        this.clipboard = clipboard;
    }
}

// 文本编辑命令
public class InsertCommand implements Command {
    private TextEditor editor;
    private String text;
    private EditorState previousState;
    
    public InsertCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        this.previousState = editor.saveState();
        editor.insertText(text);
    }
    
    @Override
    public void undo() {
        editor.restoreState(previousState);
    }
}

public class DeleteCommand implements Command {
    private TextEditor editor;
    private int length;
    private EditorState previousState;
    
    public DeleteCommand(TextEditor editor, int length) {
        this.editor = editor;
        this.length = length;
    }
    
    @Override
    public void execute() {
        this.previousState = editor.saveState();
        editor.deleteText(length);
    }
    
    @Override
    public void undo() {
        editor.restoreState(previousState);
    }
}

public class CopyCommand implements Command {
    private TextEditor editor;
    private int start;
    private int end;
    
    public CopyCommand(TextEditor editor, int start, int end) {
        this.editor = editor;
        this.start = start;
        this.end = end;
    }
    
    @Override
    public void execute() {
        editor.copyText(start, end);
    }
    
    @Override
    public void undo() {
        System.out.println("无法撤销复制操作");
    }
}

public class PasteCommand implements Command {
    private TextEditor editor;
    private EditorState previousState;
    
    public PasteCommand(TextEditor editor) {
        this.editor = editor;
    }
    
    @Override
    public void execute() {
        this.previousState = editor.saveState();
        editor.pasteText();
    }
    
    @Override
    public void undo() {
        editor.restoreState(previousState);
    }
}

public class BoldCommand implements Command {
    private TextEditor editor;
    private int start;
    private int end;
    private EditorState previousState;
    
    public BoldCommand(TextEditor editor, int start, int end) {
        this.editor = editor;
        this.start = start;
        this.end = end;
    }
    
    @Override
    public void execute() {
        this.previousState = editor.saveState();
        editor.boldText(start, end);
    }
    
    @Override
    public void undo() {
        editor.restoreState(previousState);
    }
}

// 编辑器应用 - 调用者
public class TextEditorApp {
    private TextEditor editor = new TextEditor();
    private CommandHistory history = new CommandHistory();
    
    // UI组件
    public void clickInsertButton(String text) {
        Command command = new InsertCommand(editor, text);
        history.execute(command);
    }
    
    public void clickDeleteButton(int length) {
        Command command = new DeleteCommand(editor, length);
        history.execute(command);
    }
    
    public void clickCopyButton(int start, int end) {
        Command command = new CopyCommand(editor, start, end);
        history.execute(command);
    }
    
    public void clickPasteButton() {
        Command command = new PasteCommand(editor);
        history.execute(command);
    }
    
    public void clickBoldButton(int start, int end) {
        Command command = new BoldCommand(editor, start, end);
        history.execute(command);
    }
    
    public void undo() {
        history.undo();
    }
    
    public void redo() {
        history.redo();
    }
    
    public void printStatus() {
        System.out.println("=== 编辑器状态 ===");
        System.out.println("内容: " + editor.getContent());
        System.out.println("光标位置: " + editor.getCursorPosition());
        System.out.println("剪贴板: " + editor.getClipboard());
        System.out.println("历史记录: " + history.getHistorySize() + " 个操作");
        System.out.println("可重做: " + history.getRedoSize() + " 个操作");
        System.out.println();
    }
}

// 使用示例
public class TextEditorDemo {
    public static void main(String[] args) {
        TextEditorApp app = new TextEditorApp();
        
        System.out.println("=== 文本编辑器演示 ===\n");
        
        // 执行一系列操作
        app.clickInsertButton("Hello, ");
        app.printStatus();
        
        app.clickInsertButton("World!");
        app.printStatus();
        
        app.clickCopyButton(0, 5); // 复制 "Hello"
        app.printStatus();
        
        app.clickPasteButton(); // 粘贴 "Hello"
        app.printStatus();
        
        app.clickBoldButton(6, 11); // 加粗 "World"
        app.printStatus();
        
        // 测试撤销和重做
        System.out.println("=== 测试撤销和重做 ===\n");
        
        app.undo(); // 撤销加粗
        app.printStatus();
        
        app.undo(); // 撤销粘贴
        app.printStatus();
        
        app.redo(); // 重做粘贴
        app.printStatus();
        
        app.undo(); // 再次撤销粘贴
        app.printStatus();
        
        // 宏命令演示
        System.out.println("=== 宏命令演示 ===\n");
        
        MacroCommand macro = new MacroCommand();
        macro.addCommand(new InsertCommand(app.editor, "这是"));
        macro.addCommand(new InsertCommand(app.editor, "一个"));
        macro.addCommand(new InsertCommand(app.editor, "宏命令"));
        
        app.history.execute(macro);
        app.printStatus();
        
        app.undo(); // 撤销整个宏命令
        app.printStatus();
    }
}

5. Spring框架中的命令模式

5.1 Spring的事务管理

java 复制代码
// 模拟Spring的事务命令
public interface TransactionalCommand extends Command {
    void setTransactionManager(TransactionManager transactionManager);
}

// 事务命令基类
public abstract class AbstractTransactionalCommand implements TransactionalCommand {
    protected TransactionManager transactionManager;
    protected TransactionStatus transactionStatus;
    
    @Override
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    @Override
    public void execute() {
        transactionStatus = transactionManager.getTransaction(null);
        try {
            doInTransaction();
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            transactionManager.rollback(transactionStatus);
            throw new RuntimeException("事务执行失败", e);
        }
    }
    
    @Override
    public void undo() {
        // 事务命令的撤销就是回滚
        if (transactionStatus != null && !transactionStatus.isCompleted()) {
            transactionManager.rollback(transactionStatus);
        }
    }
    
    protected abstract void doInTransaction();
}

// 具体的事务命令
@Component
public class UserRegistrationCommand extends AbstractTransactionalCommand {
    private final UserRepository userRepository;
    private final EmailService emailService;
    private User user;
    
    @Autowired
    public UserRegistrationCommand(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    public void setUser(User user) {
        this.user = user;
    }
    
    @Override
    protected void doInTransaction() {
        // 在事务中执行用户注册逻辑
        userRepository.save(user);
        emailService.sendWelcomeEmail(user.getEmail());
        
        System.out.println("用户注册成功: " + user.getUsername());
    }
}

// 事务管理器
@Component
public class TransactionManager {
    public TransactionStatus getTransaction(Object definition) {
        System.out.println("开始事务");
        return new DefaultTransactionStatus();
    }
    
    public void commit(TransactionStatus status) {
        if (status.isCompleted()) {
            throw new IllegalStateException("事务已完成");
        }
        status.setCompleted(true);
        System.out.println("提交事务");
    }
    
    public void rollback(TransactionStatus status) {
        if (status.isCompleted()) {
            throw new IllegalStateException("事务已完成");
        }
        status.setCompleted(true);
        System.out.println("回滚事务");
    }
}

// 事务状态
public class DefaultTransactionStatus implements TransactionStatus {
    private boolean completed = false;
    
    @Override
    public boolean isCompleted() {
        return completed;
    }
    
    public void setCompleted(boolean completed) {
        this.completed = completed;
    }
}

// 服务层使用事务命令
@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final TransactionManager transactionManager;
    
    @Autowired
    public UserService(UserRepository userRepository, EmailService emailService, 
                      TransactionManager transactionManager) {
        this.userRepository = userRepository;
        this.emailService = emailService;
        this.transactionManager = transactionManager;
    }
    
    public void registerUser(User user) {
        UserRegistrationCommand command = new UserRegistrationCommand(userRepository, emailService);
        command.setUser(user);
        command.setTransactionManager(transactionManager);
        
        command.execute();
    }
}

5.2 Spring的JdbcTemplate

java 复制代码
// 模拟Spring JdbcTemplate的命令模式应用
@Component
public class JdbcTemplate {
    
    public <T> T execute(ConnectionCallback<T> action) {
        return execute(action, true);
    }
    
    public <T> T execute(ConnectionCallback<T> action, boolean autoClose) {
        Connection connection = null;
        try {
            connection = getConnection();
            return action.doInConnection(connection);
        } catch (SQLException e) {
            throw new RuntimeException("数据库操作失败", e);
        } finally {
            if (autoClose && connection != null) {
                closeConnection(connection);
            }
        }
    }
    
    public <T> T query(String sql, ResultSetExtractor<T> extractor, Object... args) {
        return execute(connection -> {
            try (PreparedStatement ps = connection.prepareStatement(sql)) {
                setParameters(ps, args);
                try (ResultSet rs = ps.executeQuery()) {
                    return extractor.extractData(rs);
                }
            }
        });
    }
    
    public int update(String sql, Object... args) {
        return execute(connection -> {
            try (PreparedStatement ps = connection.prepareStatement(sql)) {
                setParameters(ps, args);
                return ps.executeUpdate();
            }
        });
    }
    
    // 命令接口
    public interface ConnectionCallback<T> {
        T doInConnection(Connection connection) throws SQLException;
    }
    
    public interface ResultSetExtractor<T> {
        T extractData(ResultSet rs) throws SQLException;
    }
    
    // 具体命令
    public class QueryCommand<T> implements ConnectionCallback<T> {
        private final String sql;
        private final ResultSetExtractor<T> extractor;
        private final Object[] args;
        
        public QueryCommand(String sql, ResultSetExtractor<T> extractor, Object... args) {
            this.sql = sql;
            this.extractor = extractor;
            this.args = args;
        }
        
        @Override
        public T doInConnection(Connection connection) throws SQLException {
            try (PreparedStatement ps = connection.prepareStatement(sql)) {
                setParameters(ps, args);
                try (ResultSet rs = ps.executeQuery()) {
                    return extractor.extractData(rs);
                }
            }
        }
    }
    
    private Connection getConnection() {
        // 获取数据库连接
        System.out.println("获取数据库连接");
        return null; // 实际返回Connection
    }
    
    private void closeConnection(Connection connection) {
        // 关闭数据库连接
        System.out.println("关闭数据库连接");
    }
    
    private void setParameters(PreparedStatement ps, Object[] args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
    }
}

6. 命令模式的进阶用法

6.1 异步命令执行

java 复制代码
// 异步命令执行器
@Component
public class AsyncCommandExecutor {
    private final ExecutorService executorService;
    private final CommandHistory history;
    
    public AsyncCommandExecutor() {
        this.executorService = Executors.newFixedThreadPool(10);
        this.history = new CommandHistory();
    }
    
    public CompletableFuture<Void> executeAsync(Command command) {
        return CompletableFuture.runAsync(() -> {
            System.out.println("异步执行命令: " + command.getClass().getSimpleName());
            history.execute(command);
        }, executorService);
    }
    
    public CompletableFuture<Void> executeAsync(List<Command> commands) {
        List<CompletableFuture<Void>> futures = commands.stream()
                .map(this::executeAsync)
                .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
    }
    
    public void shutdown() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    // 支持回调的异步执行
    public CompletableFuture<Void> executeWithCallback(Command command, Runnable onSuccess, Runnable onError) {
        return CompletableFuture.runAsync(() -> {
            try {
                history.execute(command);
                if (onSuccess != null) {
                    onSuccess.run();
                }
            } catch (Exception e) {
                if (onError != null) {
                    onError.run();
                }
            }
        }, executorService);
    }
}

// 可取消的命令
public abstract class CancellableCommand implements Command {
    private volatile boolean cancelled = false;
    private volatile Thread executionThread;
    
    @Override
    public void execute() {
        executionThread = Thread.currentThread();
        try {
            doExecute();
        } finally {
            executionThread = null;
        }
    }
    
    public void cancel() {
        cancelled = true;
        if (executionThread != null) {
            executionThread.interrupt();
        }
    }
    
    protected boolean isCancelled() {
        return cancelled || Thread.currentThread().isInterrupted();
    }
    
    protected abstract void doExecute();
}

// 长时间运行的任务命令
public class LongRunningTaskCommand extends CancellableCommand {
    private final String taskName;
    private final int durationSeconds;
    
    public LongRunningTaskCommand(String taskName, int durationSeconds) {
        this.taskName = taskName;
        this.durationSeconds = durationSeconds;
    }
    
    @Override
    protected void doExecute() {
        System.out.println("开始执行长时间任务: " + taskName);
        
        for (int i = 1; i <= durationSeconds; i++) {
            if (isCancelled()) {
                System.out.println("任务被取消: " + taskName);
                return;
            }
            
            try {
                Thread.sleep(1000);
                System.out.println(taskName + " - 进度: " + i + "/" + durationSeconds + " 秒");
            } catch (InterruptedException e) {
                System.out.println("任务被中断: " + taskName);
                Thread.currentThread().interrupt();
                return;
            }
        }
        
        System.out.println("任务完成: " + taskName);
    }
    
    @Override
    public void undo() {
        System.out.println("无法撤销长时间运行的任务: " + taskName);
    }
}

6.2 命令的序列化和持久化

java 复制代码
// 可序列化的命令
public abstract class SerializableCommand implements Command, Serializable {
    private static final long serialVersionUID = 1L;
    protected String commandId;
    protected Date createdAt;
    
    public SerializableCommand() {
        this.commandId = UUID.randomUUID().toString();
        this.createdAt = new Date();
    }
    
    public String getCommandId() {
        return commandId;
    }
    
    public Date getCreatedAt() {
        return createdAt;
    }
}

// 命令存储服务
@Component
public class CommandStore {
    private final String storagePath;
    
    public CommandStore() {
        this.storagePath = "commands/";
        createStorageDirectory();
    }
    
    public void saveCommand(SerializableCommand command) {
        String filename = storagePath + command.getCommandId() + ".cmd";
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            oos.writeObject(command);
            System.out.println("命令已保存: " + filename);
        } catch (IOException e) {
            throw new RuntimeException("保存命令失败", e);
        }
    }
    
    public SerializableCommand loadCommand(String commandId) {
        String filename = storagePath + commandId + ".cmd";
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            return (SerializableCommand) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("加载命令失败", e);
        }
    }
    
    public List<SerializableCommand> loadAllCommands() {
        File dir = new File(storagePath);
        File[] files = dir.listFiles((d, name) -> name.endsWith(".cmd"));
        
        if (files == null) {
            return Collections.emptyList();
        }
        
        return Arrays.stream(files)
                .map(file -> loadCommand(file.getName().replace(".cmd", "")))
                .sorted(Comparator.comparing(SerializableCommand::getCreatedAt))
                .collect(Collectors.toList());
    }
    
    private void createStorageDirectory() {
        File dir = new File(storagePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }
}

// 持久化文本命令
public class PersistentInsertCommand extends SerializableCommand {
    private String text;
    private transient TextEditor editor; // 不序列化
    
    public PersistentInsertCommand(TextEditor editor, String text) {
        super();
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        if (editor != null) {
            editor.insertText(text);
        } else {
            System.out.println("执行持久化命令: 插入文本 \"" + text + "\"");
        }
    }
    
    @Override
    public void undo() {
        System.out.println("撤销持久化命令: " + commandId);
    }
    
    // 用于反序列化后重新注入依赖
    public void setEditor(TextEditor editor) {
        this.editor = editor;
    }
}

7. 命令模式 vs 其他模式

7.1 命令模式 vs 策略模式

  • 命令模式:封装操作和参数,支持撤销、队列、日志等功能
  • 策略模式:封装算法,在运行时选择不同的算法实现

7.2 命令模式 vs 备忘录模式

  • 命令模式:封装操作请求,支持操作的执行和撤销
  • 备忘录模式:保存和恢复对象状态,用于实现撤销功能

7.3 命令模式 vs 责任链模式

  • 命令模式:将请求封装为对象,发送给接收者处理
  • 责任链模式:多个处理器按顺序尝试处理请求

8. 总结与思考

8.1 命令模式的优点

  1. 解耦调用者和接收者:调用者不需要知道具体的接收者
  2. 支持撤销和重做:可以轻松实现操作的撤销和重做功能
  3. 支持操作队列:可以将命令放入队列,延迟执行或批量执行
  4. 支持宏命令:可以将多个命令组合成一个复合命令
  5. 易于扩展:新增命令不需要修改现有代码

8.2 命令模式的缺点

  1. 类数量增加:每个命令都需要一个具体的命令类
  2. 复杂性增加:系统需要管理命令的历史、队列等
  3. 性能开销:命令的创建和管理可能带来性能开销
  4. 过度设计:简单场景下可能过度设计

8.3 设计思考

命令模式的本质是**"请求的对象化"**。它通过将请求封装为对象,使得请求可以被参数化、队列化、日志化和撤销化,从而提供了极大的灵活性。

深入思考的角度

"命令模式的核心价值在于它将'做什么'和'谁来做'、'什么时候做'分离开来。这种分离使得我们可以构建高度灵活和可扩展的系统,支持复杂的操作管理需求。"

在实际应用中,命令模式有很多优秀的实践:

  • GUI应用程序的菜单和按钮系统
  • 事务处理系统的操作管理
  • 任务调度系统的任务封装
  • 游戏开发中的输入处理
  • 远程过程调用(RPC)的请求封装

从系统设计的角度看,命令模式特别适合以下场景:

  • 需要将操作请求参数化
  • 需要支持操作的撤销和重做
  • 需要将操作放入队列或在不同的时间执行
  • 需要支持事务操作
  • 需要记录操作日志

最佳实践建议

  1. 仔细设计命令接口,考虑撤销、重做等需求
  2. 使用命令历史管理器来管理命令的执行历史
  3. 考虑使用宏命令来组合多个相关操作
  4. 对于耗时操作,考虑实现异步命令执行
  5. 在分布式系统中,考虑命令的序列化和持久化

使用场景判断

  • 适合:需要撤销/重做、操作队列、事务支持、宏命令的场景
  • 不适合:简单的一次性操作、性能要求极高的场景

下一篇预告:设计模式手册014 - 解释器模式:如何定义语言的文法,并用解释器来解释语言中的句子?


版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

相关推荐
方白羽1 小时前
Android多层嵌套RecyclerView滚动
android·java·kotlin
ada0_ada12 小时前
行为型模式:②命令模式(Command Pattern)
设计模式
uup2 小时前
Java 中 ArrayList 线程安全问题
java
uup2 小时前
Java 中日期格式化的潜在问题
java
老华带你飞2 小时前
海产品销售系统|海鲜商城购物|基于SprinBoot+vue的海鲜商城系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·海鲜商城购物系统
2401_837088502 小时前
Redisson的multilock原理
java·开发语言
今天你TLE了吗2 小时前
Stream流学习总结
java·学习
o0向阳而生0o3 小时前
113、23种设计模式之中介者模式(21/23)
设计模式·中介者模式
心语星光3 小时前
23种经典设计模式
设计模式