命令模式

命令模式中文讲解

命令模式(Command Pattern)是一种行为型设计模式,属于 GoF(Gang of Four)提出的23种设计模式之一。它的核心目标是将请求封装为一个对象,从而将请求的发起者与执行者解耦,支持请求的撤销、排队、记录等操作。命令模式适合需要将操作抽象化、支持灵活操作(如撤销、重做)的场景,例如编辑器操作、事务管理等。

以下用中文详细讲解命令模式的定义、结构、代码示例、应用场景、优缺点以及在 Servlet 环境中的使用,重点突出其原理和实际应用。


1. 什么是命令模式?

命令模式通过将请求(操作)封装成一个命令对象,允许客户端通过调用命令对象发起请求,而无需直接与执行者交互。它解决了以下问题:

  • 问题:客户端直接调用执行者的方法会导致紧耦合,且难以支持撤销、排队或日志记录等功能。
  • 解决方案:定义命令接口,将请求封装为对象,包含执行、撤销等方法,通过调用者(Invoker)触发命令,执行者(Receiver)处理具体逻辑。

关键特点

  • 封装请求:将操作封装为命令对象,包含所有必要信息。
  • 解耦:请求发起者与执行者分离,中间通过命令对象交互。
  • 扩展性:支持撤销、重做、排队等高级功能。

2. 命令模式的结构

命令模式包含以下角色:

角色描述
抽象命令(Command)定义命令接口,声明 execute() 方法,可能包括 undo()
具体命令(Concrete Command)实现命令接口,绑定执行者和具体操作,调用执行者的方法。
执行者(Receiver)包含实际业务逻辑,执行命令请求的操作。
调用者(Invoker)持有命令对象,触发 execute()undo()
客户端(Client)创建命令对象,设置执行者,配置调用者。

UML 类图(文字描述)

  • Command:接口,声明 execute()undo() 方法。
  • ConcreteCommand:实现 Command,持有 Receiver 引用,调用其方法。
  • Receiver:实现具体业务逻辑,如 action()
  • Invoker:持有 Command,调用 execute()undo()
  • 客户端创建 ConcreteCommand,设置 Receiver,传递给 Invoker。

3. 代码示例

以下是一个 Java 示例,模拟文本编辑器的复制和粘贴操作,支持撤销功能。

// 执行者:文本编辑器
class TextEditor {
    private StringBuilder content = new StringBuilder();

    public void copy(String text) {
        System.out.println("复制文本: " + text);
        content.append(text);
    }

    public void delete(int length) {
        if (length <= content.length()) {
            content.delete(content.length() - length, content.length());
            System.out.println("删除文本,当前内容: " + content);
        }
    }

    public String getContent() {
        return content.toString();
    }
}

// 抽象命令
interface Command {
    void execute();
    void undo();
}

// 具体命令:复制命令
class CopyCommand implements Command {
    private TextEditor editor;
    private String text;

    public CopyCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }

    @Override
    public void execute() {
        editor.copy(text);
    }

    @Override
    public void undo() {
        editor.delete(text.length());
    }
}

// 调用者:命令管理器
class CommandInvoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }

    public void undoCommand() {
        command.undo();
    }
}

// 测试
public class CommandTest {
    public static void main(String[] args) {
        // 创建执行者
        TextEditor editor = new TextEditor();

        // 创建命令
        Command copyCommand = new CopyCommand(editor, "Hello");

        // 创建调用者
        CommandInvoker invoker = new CommandInvoker();
        invoker.setCommand(copyCommand);

        // 执行命令
        invoker.executeCommand();
        System.out.println("当前内容: " + editor.getContent());

        // 撤销命令
        invoker.undoCommand();
        System.out.println("撤销后内容: " + editor.getContent());
    }
}

输出

复制文本: Hello
当前内容: Hello
删除文本,当前内容: 
撤销后内容: 

说明

  • TextEditor 是执行者,处理复制和删除逻辑。
  • Command 是抽象命令,定义 execute()undo()
  • CopyCommand 是具体命令,绑定 TextEditor 和复制操作。
  • CommandInvoker 是调用者,触发命令的执行和撤销。
  • 客户端配置命令并通过调用者触发。

4. 命令模式的应用场景

  • 操作封装:如文本编辑器的撤销/重做(复制、粘贴、删除)。
  • 事务管理:如数据库事务的提交和回滚。
  • 任务排队:如线程池的任务调度、命令队列。
  • 日志记录:记录操作历史以支持恢复。
  • Servlet 相关:封装 HTTP 请求处理,支持撤销或日志。

Servlet 示例:封装用户操作请求,支持撤销。

import javax.servlet.http.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 执行者:用户服务
class UserService {
    private List<String> users = new ArrayList<>();

    public void addUser(String username) {
        users.add(username);
        System.out.println("添加用户: " + username);
    }

    public void removeUser(String username) {
        users.remove(username);
        System.out.println("移除用户: " + username);
    }

    public String getUsers() {
        return users.toString();
    }
}

// 抽象命令
interface UserCommand {
    void execute();
    void undo();
}

// 具体命令:添加用户
class AddUserCommand implements UserCommand {
    private UserService service;
    private String username;

    public AddUserCommand(UserService service, String username) {
        this.service = service;
        this.username = username;
    }

    @Override
    public void execute() {
        service.addUser(username);
    }

    @Override
    public void undo() {
        service.removeUser(username);
    }
}

// 调用者:命令管理器
class UserCommandInvoker {
    private List<UserCommand> history = new ArrayList<>();

    public void executeCommand(UserCommand command) {
        command.execute();
        history.add(command);
    }

    public void undoLastCommand() {
        if (!history.isEmpty()) {
            UserCommand command = history.remove(history.size() - 1);
            command.undo();
        }
    }
}

// Servlet
public class UserCommandServlet extends HttpServlet {
    private final UserService userService = new UserService();
    private final UserCommandInvoker invoker = new UserCommandInvoker();

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=UTF-8");
        String action = req.getParameter("action");
        String username = req.getParameter("username");

        if ("add".equals(action) && username != null) {
            UserCommand command = new AddUserCommand(userService, username);
            invoker.executeCommand(command);
            resp.getWriter().write("<h1>添加用户成功: " + username + "</h1>");
        } else if ("undo".equals(action)) {
            invoker.undoLastCommand();
            resp.getWriter().write("<h1>撤销操作成功</h1>");
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "无效请求");
        }

        resp.getWriter().write("<p>当前用户: " + userService.getUsers() + "</p>");
    }
}

输出(POST /user?action=add&username=张三):

添加用户: 张三
<h1>添加用户成功: 张三</h1>
<p>当前用户: [张三]</p>

输出(POST /user?action=undo):

移除用户: 张三
<h1>撤销操作成功</h1>
<p>当前用户: []</p>

说明

  • UserService 是执行者,管理用户列表。
  • UserCommand 是抽象命令,定义执行和撤销。
  • AddUserCommand 封装添加用户操作。
  • UserCommandInvoker 管理命令历史,支持撤销。
  • Servlet 根据请求触发命令或撤销。

5. 命令模式的优缺点

优点

  1. 解耦:请求发起者与执行者分离,客户端无需知道执行细节。
  2. 扩展性:支持撤销、重做、排队、日志等功能。
  3. 灵活性:可动态组合命令,调整执行逻辑。
  4. 符合开闭原则:新增命令无需修改调用者。

缺点

  1. 复杂性:每个操作需定义命令类,增加代码量。
  2. 内存开销:存储命令历史(如撤销功能)可能占用内存。
  3. 适用范围有限:适合需要封装请求的场景,简单操作可能显得繁琐。

6. 注意事项

  1. 命令设计
  • 命令对象应封装所有执行信息(如参数、执行者)。
  • 撤销功能需确保可逆操作(如保存状态)。
  1. 中文编码问题
  • 如果涉及中文(如用户名、响应消息),确保 Servlet 使用 UTF-8:
    java resp.setContentType("text/html;charset=UTF-8");
  • 对中文参数编码(如 URLEncoder.encode("张三", "UTF-8"))。
  1. 线程安全
  • 命令对象通常无状态,线程安全。
  • 调用者和执行者在 Servlet 环境中需考虑多线程(如同步 history 列表)。
  1. 性能优化
  • 限制命令历史大小,避免内存溢出。
  • 缓存执行者实例,减少创建开销。
  1. 与其他模式结合
  • 策略模式:命令封装操作,策略选择算法。
  • 工厂模式:动态创建命令对象。
  • 备忘录模式:保存命令状态以支持撤销。
  1. 常见问题解决
  • 撤销失败:检查命令是否正确保存状态。
  • 内存问题:清理过期的命令历史。
  • 线程冲突:使用线程安全的集合(如 CopyOnWriteArrayList)。

7. 与其他模式的区别

特性命令模式策略模式责任链模式
目的封装请求,支持撤销动态选择算法动态传递请求
实现方式命令对象策略接口链式处理
关注点请求封装算法替换请求分发
客户端交互通过调用者触发直接调用策略通过链首触发

8. 总结

  • 命令模式通过封装请求为对象,解耦发起者和执行者,支持撤销、排队等功能。
  • 核心是抽象命令、具体命令、执行者和调用者。
  • 在 Servlet 中,可用于封装请求处理,支持撤销或日志。
  • 优点是解耦和扩展性,缺点是复杂性和内存开销。
  • 注意中文编码、线程安全和命令历史管理。

如果需要更复杂的示例(如多命令组合、备忘录模式结合)、Servlet 集成细节,或与其他模式(如策略、责任链)的深入对比,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注