设计模式---命令模式

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语句和相关的参数,然后执行这些命令。

相关推荐
市场部需要一个软件开发岗位几秒前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿4 分钟前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
MZ_ZXD0019 分钟前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
PP东11 分钟前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
ManThink Technology16 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
invicinble20 分钟前
springboot的核心实现机制原理
java·spring boot·后端
人道领域28 分钟前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
大模型玩家七七1 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
CodeToGym1 小时前
【Java 办公自动化】Apache POI 入门:手把手教你实现 Excel 导入与导出
java·apache·excel
凡人叶枫2 小时前
C++中智能指针详解(Linux实战版)| 彻底解决内存泄漏,新手也能吃透
java·linux·c语言·开发语言·c++·嵌入式开发