设计模式---命令模式

1. 简介

命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而让你可以使用不同的请求把客户端参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式通常用于以下几种情况:

  1. 解耦 调用操作的客户与执行操作的类:通过命令模式,可以使得调用操作的客户不需要知道是谁将会执行这个操作,以及如何执行。

  2. 需要对操作进行记录、排队或 日志记录:命令模式允许系统将请求记录到日志中,支持事务的撤销和恢复。

  3. 需要支持撤销和重做操作:命令模式可以很容易地实现操作的撤销和重做。

2. 结构

命令模式通常包含以下角色:

  1. Command:定义命令的接口,声明执行操作的方法。

  2. ConcreteCommand:Command 接口的实现对象,它对应于具体的行为和接收者的绑定。

  3. Client:创建具体的命令对象,并且设置它的接收者。

  4. Invoker:要求命令对象执行请求。

  5. Receiver:知道如何实施与执行一个请求相关的操作。

3. 简单实现

3.1 工程结构

java 复制代码
com.xiaokai/
├── command/
│   ├── LightOnCommand.java   // 具体命令实现,用于打开灯光
│   └── Command.java          // 命令接口
├── receiver/
│   └── Light.java            // 接收者,执行实际的开关灯操作
├── RemoteControl.java        // 调用者,用于触发命令
└── Client.java               // 客户端,用于创建命令对象和调用者对象

3.2 实现

定义命令接口

java 复制代码
/**
 * Author:yang
 * Date:2024-09-24 11:12
 * Description:Command接口
 */
public interface Command {
    /**
     * 执行命令
     */
    void execute();
    /**
     * 撤销命令
     */
    void undo();
}

具体命令实现

java 复制代码
package com.xiaokai.command.impl;

import com.xiaokai.command.Command;
import com.xiaokai.command.receiver.Light;

/**
 * Author:yang
 * Date:2024-09-24 11:14
 * Description:具体命令
 */
public class LightOnCommand implements Command {

    private Light light;

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }

    public LightOnCommand(Light light) {
        this.light = light;
    }
}

接收者

java 复制代码
package com.xiaokai.command.receiver;

/**
 * Author:yang
 * Date:2024-09-24 11:15
 * Description:接收者
 */
public class Light {

    public void on(){
        System.out.println("Light is on");
    }

    public void off(){
        System.out.println("Light is off");
    }
}

调用者

java 复制代码
package com.xiaokai.command;

/**
 * Author:yang
 * Date:2024-09-24 11:18
 * Description:调用者
 */

public class RemoteControl {

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

    /**
     * 调用命令对象的方法
     */
    public void buttonWasPressed() {
        System.out.println("Button was pressed");
        command.execute();
    }

}

客户端

java 复制代码
/**
 * Author:yang
 * Date:2024-09-24 11:20
 * Description:客户端
 */
public class Client {
    public static void main(String[] args) {
        // 创建接收者对象
        Light receiver = new Light();
        // 创建命令对象
        Command command = new LightOnCommand(receiver);
        // 创建调用者对象
        RemoteControl control = new RemoteControl();
        control.setCommand(command);
        // 执行命令
        control.buttonWasPressed();
        // 取消命令
        command.undo();
    }
}

测试结果

bash 复制代码
Button was pressed
Light is on
Light is off

4. 源码

在package java.util.concurrent包中存在一个典型的命令模式应用。

命令接口

java 复制代码
public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

命令相关执行

java 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

分 3 个步骤进行:

  1. 如果正在运行的线程少于 corePoolSize,请尝试使用给定命令作为其第一个任务启动新线程。对 addWorker 的调用以原子方式检查 runState 和 workerCount,从而通过返回 false 来防止错误警报,这些错误警报会在不应该添加线程时添加线程。

  2. 如果一个任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为自上次检查以来,现有的线程已经死亡),或者池在进入此方法后关闭了。因此,我们重新检查 state,并在必要时回滚 enqueueing(如果已停止),或者如果没有,则启动一个新线程。

  3. 如果我们无法将任务排队,那么我们尝试添加新线程。如果失败,我们知道我们已经关闭或饱和,因此拒绝了任务。

5. 应用场景

  1. 智能家居控制系统:通过一个中央应用程序控制家里的各种设备,如灯光、温控器和安全摄像头。每个控制命令都封装为一个对象,允许系统排队、顺序执行和必要时撤销命令。这种模式使得控制逻辑与设备实现解耦,便于添加新设备或功能。

  2. 图形用户界面 GUI :在桌面应用程序中,GUI按钮和菜单项通常会使用命令模式。命令模式允许用户界面与业务逻辑解耦,使得用户界面可以独立于业务逻辑进行开发和修改。

  3. 数据库事务:在数据库系统中,事务可以被视为一系列命令的集合,这些命令可以被提交(执行)或回滚(撤销)。命令模式提供了一种结构化的方式来管理这些操作,确保数据的一致性和完整性。

  4. 宏命令:在需要执行一系列命令作为单个操作时,可以将多个命令对象组合成一个宏命令。例如,在文本编辑器中,可以将一系列编辑操作(如复制、粘贴、格式设置)组合成一个宏,以便一次执行。

  5. 命令日志:在某些系统中,可能需要记录所有执行的命令以便于问题调试或恢复。命令模式允许系统将每个命令的执行记录到日志中,以便在系统崩溃后可以重新执行这些命令来恢复状态。

  6. 远程控制应用程序:例如,一个用于家庭自动化的远程控制应用程序,它可以接受命令来控制家中的各种设备,如灯光、风扇等。命令模式允许将每个设备的操作封装为命令对象,使得远程控制应用程序可以灵活地添加或修改控制命令。

  7. 文本编辑器:文本编辑器中的撤销和重做功能通常使用命令模式实现。每个编辑操作(如插入文本、删除文本)都是一个命令对象,可以被执行、撤销和重做。

  8. Java Runnable 接口 :Java中的Runnable接口是一种特殊的命令模式,它允许将任务封装为对象,这些对象可以被提交给线程池执行。

  9. Spring框架的 JdbcTemplate :Spring框架使用命令模式来执行数据库操作。JdbcTemplate使用命令对象来封装SQL语句和相关的参数,然后执行这些命令。

相关推荐
智慧老师19 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm21 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
V+zmm101341 小时前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
Oneforlove_twoforjob1 小时前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
xmh-sxh-13141 小时前
常用的缓存技术都有哪些
java
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
J不A秃V头A2 小时前
IntelliJ IDEA中设置激活的profile
java·intellij-idea
DARLING Zero two♡2 小时前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode
小池先生3 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
CodeClimb3 小时前
【华为OD-E卷-木板 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od