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

命令模式

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

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

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

应用场景

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

命令模式中的角色

  • 命令(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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【异常处理】Vue报错 Component template should contain exactly one root element.

问题描述 启动VUE项目后控制台报错&#xff1a; Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.翻译为&#xff1a;组件模板应该只包含一个根元素 查看vue代码&#xff0…

Python实战小项目-骰子模拟器+Turtle绘图

Python实战小项目-骰子模拟器Turtle绘图 骰子模拟器Turtle绘图 骰子模拟器 导入了random模块&#xff0c;该模块提供了生成随机数的功能。 定义了两个变量min_val和max_val&#xff0c;分别表示骰子的最小值和最大值。在这个例子中&#xff0c;骰子的最小值为1&#xff0c;最大…

二维码门楼牌管理系统应用场景:数据管理的智慧新选择

文章目录 前言一、数据管理部门的智慧工具二、助力决策制定与优质服务提供三、二维码门楼牌管理系统的优势四、展望未来 前言 随着科技的飞速发展&#xff0c;二维码门楼牌管理系统正逐渐成为城市管理的智慧新选择。该系统不仅提升了数据管理效率&#xff0c;还为政府和企业提…

blast原理与使用技巧,最全最详细

BLAST 序列比对 在生物信息学领域&#xff0c;序列比对是一项基础而关键的任务。它帮助研究人员识别基因、理解蛋白质功能&#xff0c;并揭示物种之间的进化关系。 本文旨在介绍BLAST&#xff08;Basic Local Alignment Search Tool&#xff09;的原理及其不同变体&#xff0c;…

买不到的数目c++

题目 输入样例&#xff1a; 4 7输出样例&#xff1a; 17 思路 一个字&#xff0c;猜。 一开始不知道怎么做的时候&#xff0c;想要暴力枚举对于特定的包装n, m&#xff0c;最大不能买到的数量maxValue是多少&#xff0c;然后观察性质做优化。那么怎么确定枚举结果是否正确呢…

「词令官网直达」网址导航分享5个最具权威的研究生考研信息平台官方网站

分享5个最具权威的研究生考研信息平台网站 1、中国研究生招生信息网 官网直达入口&#xff1a;打开「词令」关键词口令直达工具&#xff0c;输入词令「中国研究生招生信息网」搜索直达进入中国研究生招生信息网官方网站&#xff1b; 中国研究生招生信息网&#xff08;简称研…

npm ERR! code ERR_INVALID_URL报错解决

这个报错是URL错误&#xff0c;要排除两个点 npm的registry有没有搞错&#xff0c;也就是npm源有没有搞错 打开文件C:/User/<用户名>/.npmrc查看npm设置查看registry的设置有没有格式错误正确设置格式&#xff1a;registry"https://registry.npmmirror.com"或…

搜维尔科技:动作捕捉与数字时尚:Wondar Studios欧莱雅项目

来自意大利的Wondar Studios工作室&#xff0c;是一家制作与动作捕捉技术相关软件和内容的公司&#xff0c;其出品的三维角色动画均由专业动捕系统真实录制制作。 我们很高兴与大家分享Wondar Studios最新的动捕项目&#xff0c;该项目带来了身临其境的虚拟现实体验。他们与巴…

【Spring高级】第2讲:容器实现类

目录 BeanFactory实现BeanDefinition后置处理器单例bean创建后置处理器顺序总结 ApplicationContext实现ClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContextAnnotationConfigServletWebServerApplicationContext BeanFactory实…

stable diffusion的额外信息融入方式

conditioning怎么往sd中添加&#xff0c;一般有三种&#xff0c;一种是直接和latent拼一下&#xff0c;另外很多是在unet结构Spatialtransformers上加&#xff0c;和文本特征一样&#xff0c;通过cross-attention往unet上加&#xff0c;这里还需要注意一点&#xff0c;在文本嵌…

2024主流测试工具测评,总有一款适合你!

大家好&#xff01;我是测试元宝~ 在软件开发周期中&#xff0c;测试是确保产品质量的关键环节。随着企业对于软件质量的要求日益提升&#xff0c;测试人员面临着前所未有的挑战&#xff0c;“工欲善其事必先利其器”&#xff0c;选择一款高效、实用的软件测试工具&#xff0c…

Vue.js 修饰符:精准控制组件行为

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

《幸运的基督徒》Python

题目描述 有15个基督徒和15个非基督徒在海上遇险&#xff0c; 为了能让一部分人活下来不得不将其中15个人扔到海里面去&#xff0c; 有个人想了个办法就是大家围成一个圈&#xff0c;由某个人开始从1报数&#xff0c; 报到9的人就扔到海里面&#xff0c;他后面的人接着从1开始报…

★【完全二叉树】【层序遍历】判断是否是完全二叉树

【完全二叉树】【层序遍历】判断是否是完全二叉树 解法1 层序遍历 **判断是不是完全二叉树思路&#xff1a;**:star: ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- 解法1 层序遍历 判断是不是完全二叉树思路&#xff1a…

day28【LeetCode力扣】383.赎金信

day28【LeetCode力扣】383.赎金信 以后我们每期附张图啦&#xff5e;&#xff5e;&#xff5e; 1.题目描述 附上题目链接&#xff1a;赎金信 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以…

一篇文章教会你Python+selenium自动化生成测试报告

前言 批量执行完用例后&#xff0c;生成的测试报告是文本形式的&#xff0c;不够直观&#xff0c;为了更好的展示测试报告&#xff0c;最好是生成HTML格式的。 unittest里面是不能生成html格式报告的&#xff0c;需要导入一个第三方的模块&#xff1a;HTMLTestRunner 一、导…

python创建和上传自己的PyPI库

文章目录 创建和上传自己的PyPI库pypi准备文件制作PyPI包在上传前&#xff0c;先本地验证注册PyPI账户上传pypi判断python包质量之 SourceRankLibraries.io 参考 创建和上传自己的PyPI库 pypi 官方地址&#xff1a;https://pypi.org/ Python中我们经常会用到第三方的包&…

使用nginx输入端口号显示404

输入对应的端口号显示404 先检查当前nginx文件夹的路径是没有中文的查看是否没有开启nginx&#xff1a;ctrlaltdelete打开任务管理器&#xff0c;看看有没有nginx.exe进程&#xff08;一般是有两个进程&#xff09;如果没有进程说明没有打开nginx&#xff0c;查看端口号是否被…

金三银四求职季,这个AI神器助你斩获高薪Offer!

金三银四将至&#xff0c;又到了求职的高峰季&#xff0c;不管是招聘方&#xff0c;还是求职者&#xff0c;肉眼可见都会忙到飞起。 过去准备招聘 JD 或求职简历&#xff0c;都依赖人工编辑和包装&#xff0c;而眼下已进入 AI 时代&#xff0c;善用 AI 的人&#xff0c;无形中…

可惜了微软将终止对 Android Windows 子系统(WSA)的支持。因此,自 2035 年 3 月 5 日起,Windows 上的 WSA拜拜啦

微软将终止对安卓子系统WSA的支持。因此自 2035 年 3 月 5 日起&#xff0c;Windows上依赖于 WSA 的所有应用程序和游戏将不再受支持 可惜了! 多么好用的功能! 微软决定放弃了&#xff01; 还没有好好用起来&#xff0c;就结束了… 世界变化太快&#xff0c;都来不及反应了…