设计模式详解(命令模式)

命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,从而使得请求的发送者和接收者解耦。

一、命令模式的核心思想

命令模式的核心思想是将"请求"封装成为一个对象,从而使得我们可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。

二、命令模式的结构

在命令模式结构图中,通常包含以下几个角色:

  1. Command(抽象命令类):一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
  2. ConcreteCommand(具体命令类):抽象命令类的子类,实现了在抽象命令类中声明的方法。它对应具体的接收者对象,将接收者对象的动作绑定其中。
  3. Invoker(调用者):请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。
  4. Receiver(接收者):执行与请求相关的操作,它具体实现对请求的业务处理。
  5. Client(客户端):创建具体的命令对象,并且设置命令对象的接收者。这里的客户端并不是常规意义上的客户端,而是在组装命令对象和接收者,也可以将其称为装配者。

三、命令模式的实现

命令模式的实现通常涉及以下几个步骤:

  1. 定义一个抽象的Command接口,其中声明一个execute()方法。
  2. 具体的命令类(ConcreteCommand)实现这个execute()方法,将一个发送者(Invoker)的请求转换为一个或多个接收者(Receiver)的操作。
  3. 发送者(Invoker)并不知道接收者的具体实现,它只需要触发命令对象的execute()方法即可。

四、命令模式的应用场景

命令模式有广泛的应用场景,主要包括以下几种:

  1. 将各种请求以命令的形式加入到队列中,然后由相应的线程或进程逐个执行这些命令。
  2. 将一系列相关的操作封装成一个命令,要么全部执行成功,要么全部回滚。
  3. 通过保存历史命令,可以轻松地实现撤销和重做功能。
  4. 在执行命令的时候,可以同时将该命令记录到日志中,用于审计跟踪。
  5. 命令对象可以实现一个undo()方法,用于撤销之前执行的命令。
  6. 将多个命令组合成一个复合命令,可以一次性执行一系列操作。
  7. 在GUI程序中,可以使用命令模式来发出和处理用户点击按钮时表示的操作。
  8. 在多线程操作中,命令模式可以帮助程序在后台自动发出指令并处理其他业务,而不用等待线程完成操作。

五、命令模式的优点

  1. 降低了系统的耦合度:由于请求者与接收者之间通过命令对象进行交互,因此它们之间的依赖关系被解耦。
  2. 增强了系统的扩展性:新的命令可以很容易地添加到系统中,而不需要修改现有的客户端代码。
  3. 提供了命令的撤销和重做功能:通过保存历史命令,可以轻松地实现撤销和重做功能。
  4. 实现了请求的排队和日志记录:命令对象可以被存储在队列中,并在适当的时候执行。同时,也可以在执行命令的时候记录日志。

六、命令模式的缺点

  1. 增加了系统的复杂性:由于引入了抽象命令类、具体命令类、调用者和接收者等多个角色,系统的复杂性可能会增加。
  2. 可能会导致过度设计:如果系统中的命令数量并不多,或者命令的执行逻辑比较简单,那么使用命令模式可能会导致过度设计。

七、实战流程
不使用命令模式

java 复制代码
public class Editor {
    public void copy() {
        // 复制逻辑
    }

    public void paste() {
        // 粘贴逻辑
    }

    public void undo() {
        // 撤销逻辑
    }
}

public class User {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.copy();
        editor.paste();
        editor.undo();
    }
}

使用命令模式优化

java 复制代码
/**
 * 接收者
 */
public class Editor {
    public void copy() {
        System.out.println("复制内容");
    }

    public void paste() {
        System.out.println("粘贴内容");
    }

    public void undo() {
        System.out.println("撤销操作");
    }
}

/**
 * 命令接口
 */
public interface Command {

    void execute();
}

/**
 * 具体命令类
 */
public class CopyCommand implements Command {

    private Editor editor;

    public CopyCommand(Editor editor) {
        this.editor = editor;
    }

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

public class PasteCommand implements Command {

    private Editor editor;

    public PasteCommand(Editor editor) {
        this.editor = editor;
    }

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

public class UndoCommand implements Command {

    private Editor editor;

    public UndoCommand(Editor editor) {
        this.editor = editor;
    }

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

/**
 * 调用者
 */
public class Menu {

    private Command copyCommand;
    private Command pasteCommand;
    private Command undoCommand;

    public Menu(Command copyCommand, Command pasteCommand, Command undoCommand) {
        this.copyCommand = copyCommand;
        this.pasteCommand = pasteCommand;
        this.undoCommand = undoCommand;
    }

    public void clickCopy() {
        copyCommand.execute();
    }

    public void clickPaste() {
        pasteCommand.execute();
    }

    public void clickUndo() {
        undoCommand.execute();
    }
}

public class CommandTest {

    public static void main(String[] args) {
        Editor editor = new Editor();
        Command copy = new CopyCommand(editor);
        Command paste = new PasteCommand(editor);
        Command undo = new UndoCommand(editor);

        Menu menu = new Menu(copy, paste, undo);
        menu.clickCopy();
        menu.clickPaste();
        menu.clickUndo();
    }
}

八、在JDK源码中应用命令模式的例子
1、java.lang.Runnable 和 java.util.concurrent.Callable

Runnable 和 Callable 接口是命令模式的典型例子。这两个接口封装了要在另一个线程中执行的任务,具体任务内容由 run() 方法或 call() 方法来实现,线程池或者线程的调用者在需要的时候执行这些任务。

(1)命令模式结构

  1. 命令接口:Runnable 和 Callable 是命令接口,定义了 run() 和 call() 方法,表示要执行的命令。
  2. 具体命令:任何实现 Runnable 或 Callable 的类都是具体命令类,封装了实际的任务逻辑。
  3. 调用者:Thread 或线程池(如 ExecutorService),负责触发命令的执行。

Runnable 的 run() 方法是命令的执行逻辑,而 Thread 是调用者,它负责在合适的时间触发 run() 的执行。

(2)代码示例

java 复制代码
public class CommandExample {
    public static void main(String[] args) {
        // 创建一个命令(Runnable任务)
        Runnable command = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行命令: 线程中的任务");
            }
        };
        
        // 调用者:Thread 执行命令
        Thread thread = new Thread(command);
        thread.start();
    }
}

2、javax.swing.Action

在 Swing 框架中,Action 接口用于封装行为,它是典型的命令模式。Action 可以被 JButton、JMenuItem 等组件复用,从而将动作的定义与其使用解耦。

(1)命令模式结构

  • 命令接口:Action 接口。
  • 具体命令:实现 Action 接口的具体动作类,定义 actionPerformed(ActionEvent e) 方法,封装了执行逻辑。
  • 调用者:Swing 组件,如 JButton 或 JMenuItem,这些组件触发 Action 的执行。

(2)代码示例

sayHelloAction 是命令,JButton 是调用者,点击按钮会触发 actionPerformed 方法的执行。

java 复制代码
import javax.swing.*;
import java.awt.event.ActionEvent;

public class SwingCommandExample {
    public static void main(String[] args) {
        // 创建一个具体命令(Action)
        Action sayHelloAction = new AbstractAction("Say Hello") {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Hello, World!");
            }
        };
        
        // 调用者:JButton,触发命令的执行
        JButton button = new JButton(sayHelloAction);
        
        // 创建一个简单的Swing窗口
        JFrame frame = new JFrame("Command Pattern Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(button);
        frame.pack();
        frame.setVisible(true);
    }
}

3、java.util.Timer 和 java.util.TimerTask

TimerTask 是 JDK 中另一个典型的命令模式实现。TimerTask 类实现了 Runnable 接口,其 run() 方法封装了定时要执行的任务,Timer 类负责调度这些任务的执行。

(1)命令模式结构

  • 命令接口:TimerTask 是具体命令类,封装了任务逻辑。
  • 调用者:Timer,负责在指定时间触发命令。

(2)代码示例

TimerTask 的 run() 方法是封装的任务逻辑,Timer 是负责触发任务执行的调用者。

java 复制代码
import java.util.Timer;
import java.util.TimerTask;

public class TimerCommandExample {
    public static void main(String[] args) {
        // 创建一个定时器
        Timer timer = new Timer();

        // 创建一个具体命令(TimerTask)
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务");
            }
        };

        // 调用者:Timer,调度任务
        timer.schedule(task, 1000); // 1秒后执行任务
    }
}
相关推荐
晨米酱13 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机18 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机19 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机19 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机19 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤20 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式