设计模式—命令模式:探索【命令模式】的奥秘与应用实践!

news2024/11/16 5:52:10

命令模式

命令模式是一种行为设计模式,它的主要目的是将请求封装成一个对象,从而使得请求的发送者接收者之间进行解耦

在命令模式中,命令被封装为一个对象包含了需要执行的操作以及执行这些操作所需的所有参数

命令的发送者不需要知道接收者的具体情况,也不需要知道命令如何被执行,只需要将命令对象发送给接收者,由接收者来解析并执行命令。

应用场景

  • 请求者创建命令,消费者根据命令动态做出响应的场景
  • 撤销和恢复操作:例如文本编辑器中的撤销和恢复功能。
  • 请求日志记录:可以记录用户操作的日志,以便后期查看。
  • 任务队列:例如线程池中的任务队列,可以将命令放入队列中异步执行。
  • 菜单项操作:例如图形界面中的菜单项单击、快捷键操作等。

命令模式中的角色

  • 命令(Command): 命令是一个抽象的对象,封装了执行特定操作的方法。它通常包含一个执行操作的接口,可能还包含撤销操作的接口。
  • 具体命令(Concrete Command): 具体命令是命令的具体实现,它实现了命令接口中定义的方法。 每一个具体命令都与一个特定的接收者相关联,负责调用接收者执行请求的操作。
  • 接收者(Receiver): 接收者是实际执行命令操作的对象。它包含了命令执行时所需的具体逻辑。
  • 调用者/发送者(Invoker/Sender): 调用者是命令的发送者,它负责创建命令对象并将其发送给接收者。
  • 客户端(Client): 客户端创建具体命令对象,并将其与相应的接收者关联起来,然后将命令对象传递给调用者。

优点

  • **解耦:**发送者和接收者之间的解耦,发送者不需要知道接收者的具体实现,只需要知道如何发送命令。
  • **扩展性:**易于添加新的命令和接收者,不会影响到已有的代码。
  • **支持撤销和重做操作:**可以通过保存命令历史来支持撤销和重做操作。

缺点

  1. 可能导致类爆炸:为每个操作创建一个具体的命令类可能会导致类爆炸,尤其是在具有大量命令的系统中。
  2. 命令的逻辑混乱:如果命令的逻辑比较复杂,命令模式可能会导致命令的逻辑混乱。

Java中的注意事项

  1. 合理的接口设计:命令接口应该设计得简洁清晰,易于使用。
  2. 抽象类的使用:可以使用抽象类来实现部分命令共同的行为,以避免重复代码。
  3. 命令的实现方式:根据业务需求选择合适的命令实现方式,如简单命令、宏命令等。
  4. 线程安全性:在多线程环境下使用命令模式时,需要考虑命令对象的线程安全性。

1.案例1-小厨房(JavaSE版本)image-20240204144057601

# 餐厅,服务员先根据客户创建订单,并将订单发送给厨师,厨师根据具体的订单生产

1.1.创建命令接口

/**
 * 抽象命令接口
 */
public interface CookCommand {
	/**
	 * 执行命令
	 */
	void execute();
}

1.2.创建传输的参数

import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;

@Getter @Setter
public class Order {
	// 订单数量
	private final List<Menu> orderList;

	public Order() {
		this.orderList = new ArrayList<Menu>();
	}

	// 创建订单
	public void createOrder(Menu menu){
		orderList.add(menu);
	}
	// 获取订单
	public List<Menu> getOrderList(){
		return orderList;
	}

	@Getter @Setter
	static class Menu{
		private String menuName; // 菜品名称
		private Integer num; // 菜品数量
	}

}

1.3.创建接收者

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.util.List;

/**
 * 命令接收人
 * @author 13723
 * @version 1.0
 * 2024/2/4 11:08
 */
public class Cook {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public void makeMenu(List<Order.Menu> menuList){
		logger.info("-------------------------> 厨师准备开始做菜 <-------------------------");
		for (Order.Menu menu : menuList) {
			logger.info("厨师正在做菜:{},数量:{}",menu.getMenuName(),menu.getNum());
		}
		logger.info("-------------------------> 厨师准备结束做菜 <-------------------------");
	}
}

1.4.创建具体命令

/**
 * 具体执行命令
 * @author 13723
 * @version 1.0
 * 2024/2/4 11:15
 */
public class CookCommandImpl implements CookCommand{
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());	
	// 厨师
	private Cook cook;
	// 订单
	private List<Order.Menu> orderList;
	CookCommandImpl(){

	}
	public CookCommandImpl(Cook cook,List<Order.Menu> orderList){
		this.cook = cook;
		this.orderList = orderList;
	}
	/**
	 * 执行任务
	 */
	@Override
	public void execute() {
		cook.makeMenu(orderList);
	}
}

1.5.创建调用者或者发送者

此处写在测试方法里了

/**
 * 命令模式 测试
 * @author 13723
 * @version 1.0
 * 2024/2/4 14:09
 */
public class CommandTest {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());


	@Test
	@DisplayName("测试命令模式-点餐,上菜")
	public void  test1(){
		// 创建订单
		Order order = new Order();
		order.createOrder(new Order.Menu(){{setMenuName("土豆丝");setNum(1000);}});
		order.createOrder(new Order.Menu(){{setMenuName("拍黄瓜");setNum(5);}});
		order.createOrder(new Order.Menu(){{setMenuName("红烧肉");setNum(10);}});
		order.createOrder(new Order.Menu(){{setMenuName("黄焖鸡");setNum(30);}});

		// 发送命令到执行者
		CookCommandImpl cookCommand = new CookCommandImpl(new Cook(), order.getOrderList());
		// 执行命令
		cookCommand.execute();
	}
}

image-20240204144811442

2.案例2-撤销订单(SpringBoot版本)

# 我们假设我们有一个简单的订单管理系统,用户可以执行添加订单、删除订单等操作,并且希望能够撤销(bug很多没考虑)之前的操作。

2.1.定义注解

import org.springframework.stereotype.Service;
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Command {

	/**
	 * 标记具体事务的业务类型
	 */
	String type()  default "";
}

2.2.定义订单命令接口

import com.hrfan.java_se_base.base_mvc.model.DecOrderHead;
/**
 * 定义订单命令接口
 */
public interface OrderCommand {
    /**
     * 执行命令
     */
    void execute(DecOrderHead order);

    /**
     * 撤销命令
     */
    void undo(DecOrderHead order);
}

2.3.定义命令的具体实现类

import com.fasterxml.jackson.core.type.TypeReference;
import com.hrfan.java_se_base.base_mvc.mapper.DecOrderHeadMapper;
import com.hrfan.java_se_base.base_mvc.model.DecOrderHead;
import com.hrfan.java_se_base.base_mvc.service.DecOrderHeadService;
import com.hrfan.java_se_base.common.json.JsonObjectMapper;
import com.hrfan.java_se_base.pattern.commond_pattern.se.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.lang.invoke.MethodHandles;
import java.util.Date;

/**
 * 实现 添加订单命令 和 撤销订单命令 具体执行逻辑
 * @author 13723
 * @version 1.0
 * 2024/2/4 16:07
 */
@Service
@Command(type = "insert")
public class InsertOrderCommand implements OrderCommand{
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
	@Resource
	private DecOrderHeadMapper mapper;
	@Override
	public void execute(DecOrderHead order) {
		logger.error("----------------- 添加操作开始 -----------------");
		logger.error("添加用户");
		// 留存备份(上一步操作前数据记录)
		String json = JsonObjectMapper.getInstance().toJson(order);
		order.setBeforeInfo(json);
		int insert = mapper.insert(order);
		logger.error(insert > 0 ? "添加成功!":"添加失败!");
	}

	/**
	 * 撤销操作(此撤销 仅撤销一次,真正撤销需要考虑是否撤销到第一次,例如增加计数,改变+1 撤销-1 到为 1时,此时不能在撤销)
	 * @param order 订单信息
	 */
	@Override
	public void undo(DecOrderHead order) {
		logger.error("----------------- 撤销操作开始 -----------------");
		DecOrderHead decOrderHead = mapper.selectById(order.getSid());
		Assert.notNull(decOrderHead,"订单未找到,撤销失败!");
		logger.error("撤销用户!");
		String beforeInfo = order.getBeforeInfo();
		DecOrderHead tempOrderHead = JsonObjectMapper.getInstance().fromJson(beforeInfo, new TypeReference<DecOrderHead>() {});
		tempOrderHead.setBeforeTime(new Date());
		tempOrderHead.setBeforeUser("System");
		mapper.deleteById(order);
		int insert = mapper.insert(tempOrderHead);
		logger.error(insert > 0 ? "撤销成功!":"撤销失败!");
	}
}

2.4.定义命令管理类

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;


/**
 * 定义命令管理器,用于将所有的使用注解的类注入到map中
 * 在使用的时候,直接从map中获取对应的命令,然后执行。
 */
public class OrderCommandHistoryManager {

    /**
     * 存放 各种类型的具体命令
     * key :对应的业务乐星
     * value:对应的命令
     */
    private Map<String,OrderCommand> handlerMap;

    public void setHandlerMap(List<OrderCommand> handlers) {
        handlerMap = scanHandlers(handlers);
    }

    /**
     * 扫描使用@Command注解的类 然后将其注入到map中
     * @param handlers 使用了@Command注解的类
     * @return 返回map集合
     */
    private Map<String, OrderCommand> scanHandlers(List<OrderCommand> handlers) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Command.class));
        Map<String, OrderCommand> handlerMap = new HashMap<>();
        handlers.forEach((it) -> {
            Class<?> aClass = it.getClass();
            if (hasDutyAnnotation(aClass)) {
                // 判断注解上的类型是否填写
                String type = getOrderAnnotationValue(aClass);
                // 将类型放到map中
                handlerMap.put(type, it);
            }
        });
        return handlerMap;
    }


    /**
     * 判断该类是否是 使用使用了Command注解
     * @param clazz 类名称
     * @return true使用了
     */
    private boolean hasDutyAnnotation(Class<?> clazz ) {
        return AnnotationUtils.findAnnotation(clazz, Command.class) != null;
    }


    /**
     * 判断使用了的注解@Command 是否填写了type 类型
     * @param clazz 类名称
     * @return 返回类型
     */
    private String getOrderAnnotationValue(Class<?> clazz) {
        Command command = AnnotationUtils.findAnnotation(clazz, Command.class);
        if (command == null) {
            throw new IllegalStateException("Adapter annotation not found for class: " + clazz);
        }
        return command.type();
    }


    /**
     * 获取map集合
     * @return 返回map集合
     */
    public Map<String,OrderCommand> getHandlerMap() {
        return handlerMap;
    }

    
}

2.5.定义配置类

/**
 * 命令管理类的配置类,用于将OrderCommandHistoryManager 注入到Spring的Bean中
 */
@Configuration
public class OrderCommandConfiguration {
    @Bean
    public OrderCommandHistoryManager handlerOrderCommandExecute(List<OrderCommand> handlers) {
        // 创建对象 会获取全部使用注解的类,将其注入到对应map集合中
        OrderCommandHistoryManager manager = new OrderCommandHistoryManager();
        manager.setHandlerMap(handlers);
        return manager;
    }
}

2.6.定义公共调用服务

import com.hrfan.java_se_base.base_mvc.model.DecOrderHead;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.lang.invoke.MethodHandles;

/**
 * 定义命令模式的公共服务(将所有的命令在此处进行处理)
 * 将此方法对外暴露,供其他模块调用,隐藏具体的命令实现,只暴露接口
 * @author 13723
 * @version 1.0
 * 2024/2/4 17:45
 */
@Service
public class CommandCommonExecuteService {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
	@Autowired
	private OrderCommandHistoryManager orderCommandHistoryManager;

	public void executeCommand(String type, DecOrderHead orderHead) {
		OrderCommand command = orderCommandHistoryManager.getHandlerMap().get(type);
		Assert.notNull(command,"no corresponding command found!");
		command.execute(orderHead);
	}

	public void undoLastCommand(String type,DecOrderHead orderHead) {
		OrderCommand command = orderCommandHistoryManager.getHandlerMap().get(type);
		Assert.notNull(command,"no corresponding command found!");
		command.undo(orderHead);
	}
}

2.7.测试订单管理

import com.hrfan.java_se_base.base_mvc.model.DecOrderHead;
import com.hrfan.java_se_base.common.json.JsonObjectMapper;
import com.hrfan.java_se_base.pattern.commond_pattern.boot.CommandCommonExecuteService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.lang.invoke.MethodHandles;
import java.util.Date;
import java.util.UUID;

/**
 * @author 13723
 * @version 1.0
 * 2024/2/5 07:09
 */
@SpringBootTest
public class OrderCommandTest {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	@Autowired
	private CommandCommonExecuteService commandCommonService;

	@Test
	@DisplayName("测试命令模式")
	public void test(){
		logger.error("测试命令模式");
		DecOrderHead decOrderHead = new DecOrderHead() {{
			setSid(UUID.randomUUID().toString());
			setOrderNo("123456");
			setOrderPrice(100);
			setInsertTime(new Date());
			setInsertUser("Jack");
		}};
		// 添加订单前,将订单信息备份
		decOrderHead.setBeforeInfo(JsonObjectMapper.getInstance().toJson(decOrderHead));
		// 添加订单
		commandCommonService.executeCommand("insert",decOrderHead);
		// 撤销订单
		commandCommonService.undoLastCommand("insert",decOrderHead);
	}
}

image-20240205072515014

image-20240205074025556

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1486499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【详识JAVA语言】抽象类和接口

抽象类 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 比如&#xff1a;…

经典的算法面试题(1)

题目&#xff1a; 给定一个整数数组 nums&#xff0c;编写一个算法将所有的0移到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 注意&#xff1a;必须在原数组上操作&#xff0c;不能拷贝额外的数组。尽量减少操作次数。 这…

[Redis]——Redis命令手册set、list、sortedset

&#x1f333;List类型常见命令 LPUSH / RPUSH [KEY] [element] …… 向列表左侧或者右侧插入一个或多个元素 LPOP / RPOP [key] 删除左边或者右边第一个元素 LRANGE [key] start end 返回索引start到end的元素&#xff08;索引从0开始&#xff09; BLPOP / BRPOP [key] [等…

Vue.js+SpringBoot开发社区买菜系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 数据中心模块2.1.2 菜品分类模块2.1.3 菜品档案模块2.1.4 菜品订单模块2.1.5 菜品收藏模块2.1.6 收货地址模块 2.2 可行性分析2.3 用例分析2.4 实体类设计2.4.1 菜品分类模块2.4.2 菜品档案模块2.4.3…

使用 frp 实现 windows 远程

前提条件&#xff1a; 拥有一台公网 ip 的服务器&#xff1b; 被远程控制的 windows 系统为专业版&#xff08;家庭版的其它方式没有尝试过&#xff09;&#xff1b; frp 下载包及使用说明 frp release 包的下载网址&#xff1a;https://github.com/fatedier/frp/releases 中…

从零开始搭建web组态

成果展示&#xff1a;by组态[web组态插件] 一、技术选择 目前只有两种选择&#xff0c;canvas和svg Canvas: 是一个基于像素的渲染引擎&#xff0c;使用JavaScript API在画布上绘制图像&#xff0c;它的优点包括&#xff1a; Canvas渲染速度快&#xff0c;适合处理大量图像和…

芯来科技发布最新NI系列内核,NI900矢量宽度可达512/1024位

参考&#xff1a;芯来科技发布最新NI系列内核&#xff0c;NI900矢量宽度可达512/1024位 (qq.com) 本土RISC-V CPU IP领军企业——芯来科技正式发布首款针对人工智能应用的专用处理器产品线Nuclei Intelligence(NI)系列&#xff0c;以及NI系列的第一款AI专用RISC-V处理器CPU IP…

第15集《灵峰宗论导读》

《灵峰宗论》导读。诸位法师&#xff0c;诸位同学&#xff0c;阿弥陀佛&#xff01;&#xff08;阿弥陀佛&#xff01;&#xff09; 请大家打开讲义第44面。 我们这次的《灵峰宗论》基本上是分五科&#xff0c;这五科就是发心、正见、持戒、止观跟净土。那么这五科我们基本上…

A Brief Introduction of the Tqdm Module in Python

DateAuthorVersionNote2024.02.28Dog TaoV1.0Release the note. 文章目录 A Brief Introduction of the Tqdm Module in PythonIntroductionKey FeaturesInstallation Usage ExamplesBasic UsageAdvanced Usage A Brief Introduction of the Tqdm Module in Python Introducti…

【详识JAVA语言】面向对象程序三大特性之三:多态

多态 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 多态实现条件 在java中要实现多态&#xff0c;必须要满足如下几个条件&#xff0c;缺一不可&#xf…

golang实现openssl自签名双向认证

第一步&#xff1a;生成CA、服务端、客户端证书 1. 生成CA根证书 生成CA证书私钥 openssl genrsa -out ca.key 4096创建ca.conf 文件 [ req ] default_bits 4096 distinguished_name req_distinguished_name[ req_distinguished_name ] countryName …

【Web】速谈FastJson反序列化中JdbcRowSetImpl的利用

目录 简要原理分析 exp 前文&#xff1a;【Web】速谈FastJson反序列化中TemplatesImpl的利用 简要原理分析 前文的TemplatesImpl链存在严重限制&#xff0c;即JSON.parseObject()需要开启Feature.SupportNonPublicField fastjson的第二条链JdbcRowSetImpl&#xff0c;主要…

(亲测可用)Adobe Photoshop 2024下载与安装

背景介绍&#xff1a;Adobe Photoshop 2024 是全球最受欢迎的图像编辑软件之一&#xff0c;2024年的版本带来了一系列令人印象深刻的功能&#xff1a; AI增强的自动选择和蒙版工具&#xff1a;现在&#xff0c;用户可以更轻松地选择和处理复杂的图像元素。更多的3D渲染功能&…

2023.3.3周报

目录 摘要 一、文献阅读 1、题目 2、摘要 3、模型架构 4、文献解读 一、Introduction 二、实验 三、结论 二、PINN 一、PINN比传统数值方法有哪些优势 二、PINN方法 三、正问题与反问题 三、PINN实验 一、数学方程 二、模型搭建 总结 摘要 本周我阅读了一篇…

(二)逻辑回归与交叉熵--九五小庞

什么是逻辑回归 线性回归预测的是一个连续值&#xff0c;逻辑回归给出的“是”和“否”的回答 Singmoid sigmoid函数是一个概率分布函数&#xff0c;给定某个输入&#xff0c;它将输出为一个概率值 逻辑回归损失函数 平方差所惩罚的是与损失为同一数量级的情形&#xff0…

数据结构——基本术语和概念

目录 1.数据 2.数据元素 3.数据项 4.数据对象 数据元素与数据对象 5.数据结构 1.逻辑结构 逻辑结构的种类 划分方式1 1.线性结构 2.非线性结构 ​ 划分方式2——四类基本逻辑结构 2.物理结构&#xff08;存储结构&#xff09; 1.顺序存储结构 2.链接存储结构 3…

软件实例,佳易王账单账本记账汇总统计管理系统软件教程

软件实例&#xff0c;佳易王账单账本记账汇总统计管理系统软件教程 一、前言 以下软件程序教程 以 佳易王账单记账汇总统计管理系统软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 账单可以记录 1、收入明细 2、支出明细 3、客户…

JetCache源码解析——API实现(持续更新中……)

在JetCache中不仅可以通过在类和接口的函数上使用注解Cached、CacheUpdate和CacheInvalidate等实现缓存加载、更新和删除操作&#xff0c;也支持通过调用API接口的形式来实现缓存的加载、更新和删除操作。 缓存接口 缓存接口的定义如下&#xff1a; /*** 缓存接口&#xff0…

15.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-发送通信数据包至分析工具

上一个内容&#xff1a;14.数据包分析工具界面与通信设计 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;2d6491e3c51a1a7ab4da0ee6dc4cf566a80fd6e1 代码下载地址&#xff0c;在 titan 目录下&…

LeetCode--42

42. 接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,…