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

命令模式(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秒后执行任务
    }
}
相关推荐
哪 吒7 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10227 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神10 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术11 小时前
23种设计模式速记法
设计模式
小白不太白95012 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式
小白不太白95012 小时前
设计模式之 原型模式
设计模式·原型模式
澄澈i12 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
小白不太白95019 小时前
设计模式之建造者模式
java·设计模式·建造者模式
菜菜-plus21 小时前
java 设计模式 模板方法模式
java·设计模式·模板方法模式
萨达大21 小时前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式