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

命令模式

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

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

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

应用场景

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

命令模式中的角色

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

优点

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

缺点

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

Java中的注意事项

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

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

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

1.1.创建命令接口

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

1.2.创建传输的参数

java 复制代码
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.创建接收者

java 复制代码
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.创建具体命令

java 复制代码
/**
 * 具体执行命令
 * @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.创建调用者或者发送者

此处写在测试方法里了

java 复制代码
/**
 * 命令模式 测试
 * @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();
	}
}

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

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

2.1.定义注解

java 复制代码
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.定义订单命令接口

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

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

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

java 复制代码
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.定义命令管理类

java 复制代码
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.定义配置类

java 复制代码
/**
 * 命令管理类的配置类,用于将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.定义公共调用服务

java 复制代码
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.测试订单管理

java 复制代码
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);
	}
}
相关推荐
matrixlzp2 小时前
Java 责任链模式 减少 if else 实战案例
java·设计模式
编程、小哥哥4 小时前
设计模式之组合模式(营销差异化人群发券,决策树引擎搭建场景)
决策树·设计模式·组合模式
hxj..6 小时前
【设计模式】外观模式
java·设计模式·外观模式
吾与谁归in6 小时前
【C#设计模式(10)——装饰器模式(Decorator Pattern)】
设计模式·c#·装饰器模式
无敌岩雀7 小时前
C++设计模式行为模式———命令模式
c++·设计模式·命令模式
In_life 在生活17 小时前
设计模式(四)装饰器模式与命令模式
设计模式
瞎姬霸爱.17 小时前
设计模式-七个基本原则之一-接口隔离原则 + SpringBoot案例
设计模式·接口隔离原则
鬣主任18 小时前
Spring设计模式
java·spring boot·设计模式
程序员小海绵【vincewm】20 小时前
【设计模式】结合Tomcat源码,分析外观模式/门面模式的特性和应用场景
设计模式·tomcat·源码·外观模式·1024程序员节·门面模式
丶白泽20 小时前
重修设计模式-行为型-命令模式
设计模式·命令模式