命令模式
命令模式是一种行为设计模式,它的主要目的是将请求封装成一个对象,从而使得请求的发送者和接收者之间进行解耦。
在命令模式中,命令被封装为一个对象,包含了需要执行的操作以及执行这些操作所需的所有参数。
命令的发送者不需要知道接收者的具体情况,也不需要知道命令如何被执行,只需要将命令对象发送给接收者,由接收者来解析并执行命令。
应用场景
- 请求者创建命令,消费者根据命令动态做出响应的场景
- 撤销和恢复操作:例如文本编辑器中的撤销和恢复功能。
- 请求日志记录:可以记录用户操作的日志,以便后期查看。
- 任务队列:例如线程池中的任务队列,可以将命令放入队列中异步执行。
- 菜单项操作:例如图形界面中的菜单项单击、快捷键操作等。
命令模式中的角色
- 命令(Command): 命令是一个抽象的对象,封装了执行特定操作的方法。它通常包含一个执行操作的接口,可能还包含撤销操作的接口。
- 具体命令(Concrete Command): 具体命令是命令的具体实现,它实现了命令接口中定义的方法。 每一个具体命令都与一个特定的接收者相关联,负责调用接收者执行请求的操作。
- 接收者(Receiver): 接收者是实际执行命令操作的对象。它包含了命令执行时所需的具体逻辑。
- 调用者/发送者(Invoker/Sender): 调用者是命令的发送者,它负责创建命令对象并将其发送给接收者。
- 客户端(Client): 客户端创建具体命令对象,并将其与相应的接收者关联起来,然后将命令对象传递给调用者。
优点
- **解耦:**发送者和接收者之间的解耦,发送者不需要知道接收者的具体实现,只需要知道如何发送命令。
- **扩展性:**易于添加新的命令和接收者,不会影响到已有的代码。
- **支持撤销和重做操作:**可以通过保存命令历史来支持撤销和重做操作。
缺点
- 可能导致类爆炸:为每个操作创建一个具体的命令类可能会导致类爆炸,尤其是在具有大量命令的系统中。
- 命令的逻辑混乱:如果命令的逻辑比较复杂,命令模式可能会导致命令的逻辑混乱。
Java中的注意事项
- 合理的接口设计:命令接口应该设计得简洁清晰,易于使用。
- 抽象类的使用:可以使用抽象类来实现部分命令共同的行为,以避免重复代码。
- 命令的实现方式:根据业务需求选择合适的命令实现方式,如简单命令、宏命令等。
- 线程安全性:在多线程环境下使用命令模式时,需要考虑命令对象的线程安全性。
1.案例1-小厨房(JavaSE版本)
# 餐厅,服务员先根据客户创建订单,并将订单发送给厨师,厨师根据具体的订单生产
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();
}
}
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);
}
}