命令模式

命令模式概述

  1. 什么是命令模式: 是一种行为型设计模式,将命令与具体实现进行解耦,将命令封装为对象数据进行传递,命令只关注与"执行",“撤销执行”,而命令具体执行的是什么,有下一层的接收者根据命令对象的不同来决定
  2. 命令模式的优点: 将请求与具体行为解耦,提高扩展性方便添加新的命令,可以比较容易的设置组合命令,记录命令,通过记录命令实现命令撤销

分析命令模式中的角色

  • 接收者Receiver: 具体产品功能类,执行功能动作

  • 命令Command: 为所有命令抽象的的父接口,提供执行命令的execute()与撤销执行命令的undo()方法

  • 具体命令一 ConcreteCommand1: 空命令,根据需求,不是必须创建的,实现命令接口,重写空的执行与撤销执行方法,在初始化时,并未真实对命令进行设置,此时执行命令调用的是空命令中的空方法

  • 思考问题: 命令与功能进行解耦后,命令只关注与"执行",“撤销执行”,那命令执行的是什么? 命令执行的功能功能是什么?就好像上面,实现了命令接口的空命令,命令执行,执行的是空方法,也就是什么都不执行根据产品与功能,实现命令接口创建"产品功能命令执行"子类,重写命令执行抽象方法"产品功能命令"子类中持有产品对象,命令执行方法中通过产品对象调用功能方法,由此处可以看出,当一个系统中功能命令过多时,使用命令模式,则会造成命令类过于庞大

  • 具体命令二 Concrete1Command1: 可能有多个,根据产品与功能两个维度创建的,我理解为命令与指定功能动作的绑定,该角色持有接收者,实现命令接口,重写执行命令与撤销命令方法,当执行命令是通过持有的接受者对象调用指定功能方法

  • 请求者Invoker: 我理解为用来存放不同命令的容器,同时也是客户端触发命令执行,客户端与命令的解耦层,定义了用来存放不同类型命令的容器,定义了初始化命令的方法,设置命令的方法,与执行命令的方法

命令模式流程图(不是UML)

在这里插入图片描述

命令模式代码示例

案例与分析

  • 案例: 通过命令模式设计遥控器开灯关灯
  • 产品功能: “开灯”,“关灯”
  • 命令: “执行”,“撤销执行”
  1. 创建产品功能类,对应角色Receiver
//创建"灯",灯可以实现"开灯","关灯"两个功能
class LightReceiver{

    public void on() {
        System.out.println("打开电灯");
    }
    public void off() {
        System.out.println("关闭电灯");
    }
}
  1. 创建命令父接口,对应角色Command
//创建执行命令接口:抽象出接口的原因: 为了以后更好的扩展,
//现在的示例是只针对"灯"发送命令执行,假设后续需求增加
//要对电视,洗衣机...也发送命令怎么办?
//命令与功能进行解耦后,命令只关注执行与停止撤销执行,所以定
//义两个抽象方法,根据需求来的,例如在某些场景下,解耦后只有
//"执行"一种命令,"执行打开","执行关闭","执行xxx"都是执行
abstract class Command{
    //执行动作
    abstract void execute();
    //撤销动作
    abstract void undo();
}

  1. 创建具体命令实现子类一,空命令,对应角色ConcreteCommand1
//实现命令接口创建执行命令的实现子类一"空命令"类
//创建空命令的原因是在初始存放执行命令的容器
//时,并没有实际存放命令,专门用初始化命令的
//假设需求中在初始化命令容器时可以直接指定存放的
//命令类型,则可以不使用该类
//执行时命令对象调用执行方法,则执行此处的空方法
class NoCommand extends Command{

    @Override
    public void execute() {

    }

    @Override
    public void undo() {

    }
}
  1. 根据产品功能创建具体命令实现子类,命令与执行的动作进行绑定,对应角色Concrete1Command1:“灯"有两个功能"开灯”,“关灯”,执行灯的命令就是"执行开灯",“执行关灯”

开灯命令类

//根据产品与功能创建执行命令实现子类二: "开灯命令类",该类中重点关注"开灯"功能
class LightOnCommand extends Command{
    //持有灯对象
    public LightReceiver light;
    //通过构造器赋值
    public LightOnCommand(LightReceiver light) {
        this.light = light;
    }
    //重写执行命令接口: 该命令为"开灯"命令
    //当执行该命令时,通过持有的产品对象"灯"light
    //调用开灯方法 on()
    @Override
    public void execute() {
        light.on();
    }

    //重写撤销执行方法,通过light调用关灯方法
    @Override
    public void undo() {
        light.off();
    }
}

关灯命令类

//根据产品与功能创建"执行命令"实现子类三: "关灯命令"类,该类中重点关注"关灯"功能
class LightOffCommand extends Command{
    //持有产品对象
    public LightReceiver light;
    //通过构造器赋值
    public LightOffCommand(LightReceiver light) {
        this.light = light;
    }

    //重写命令执行方法,方法中通过产品调用功能方法
    @Override
    public void execute() {
        light.off();
    }
    //重写撤销命令方法,方法中通过产品调用执行前的方法
    @Override
    public void undo() {
        light.on();
    }
}
  1. 创建持有命令,接收客户端请求,通过持有命令,执行功能的遥控器 对应角色Invoker
//遥控器,我理解为用来存放不同命令的容器,
//同时也是客户端触发命令执行,客户端与命令的解耦层
//定义了用来存放不同类型命令的容器,定义了初始化命令的方法
//设置命令的方法,与执行命令的方法
class RemoteController{
    //存放命令的容器(此处使用数组,分别定义3个,用来存放不同类型的命令)
    //存放"开"命令容器
    public Command[] onCommands;
    //存放"关"命令容器
    public Command[] offCommands;
    //记录上次执行的命令
    public Command undoCommand;

    //构造器完成命令初始化,注意由于在最初初始化时,并不知道
    //该"开"或"关"的命令是针对哪个设备的,所以使用NoCommand
    //假设遥控器有5个按钮,所以初始化了5个命令
    public RemoteController() {
        onCommands = new Command[5];
        offCommands = new Command[5];
        for(int i = 0; i<5; i++) {
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }
    }

    //给指定按钮设置需要的命令(设置命令)
    public void setCommand(int num, Command onCommand, Command offCommand) {
        onCommands[num] = onCommand;
        offCommands[num] = offCommand;
    }

    //按下开的按钮(执行命令)
    public void buttonOnCommand(int num) {
        onCommands[num].execute();
        //记录此次的操作,供撤销使用
        undoCommand = onCommands[num];
    }

    //按下关的按钮(执行命令)
    public void buttonOffCommand(int num) {
        offCommands[num].execute();
        //记录此次的操作,供撤销使用
        undoCommand = offCommands[num];
    }

    //按下撤销(执行命令)
    public void buttonUndo() {
        undoCommand.undo();
    }
}
  1. 客户端调用
	public static void main(String[]args) {
        //创建点灯对象
        LightReceiver lightReceiver = new LightReceiver();

        //创建电灯开关命令
        LightOnCommand lightOn = new LightOnCommand(lightReceiver);
        LightOffCommand lightOff = new LightOffCommand(lightReceiver);

        //创建遥控器
        RemoteController remote = new RemoteController();

        //给遥控器设置命令
        remote.setCommand(0, lightOn, lightOff);

        //按下灯的开按钮
        remote.buttonOnCommand(0);
        //按下灯的撤销按钮
        remote.buttonUndo();

        //按下灯的关按钮
        remote.buttonOffCommand(0);
        //按下灯的撤销按钮
        remote.buttonUndo();
    }

根据代码分析

我理解的命令模式是: 将命令与实际功能进行剥离,创建命令类与功能类,命令类中提供命令执行的方法,然后新增命令与功能进行绑定的类,例如开灯命令,关灯命令,命令功能绑定类继承命令类,并持有功能类对象,重写执行命令的方法,在方法中通过持有的功能类对象调用功能方法,进而实现命令执行功能执行

业务与设计模式落地案例

  1. 命令模式和状态模式有一些相似之处,两者都能够将行为抽象出来并通过聚合来进行切换,区别在于目的和解决的问题
  1. 命令模式的目的是将请求发送者和接收者解耦,使得请求可以灵活地发出和处理,从而提高系统的灵活性和可扩展性
  2. 状态模式的目的是将状态的变化和状态相关的行为封装起来,使得状态的变化对外部不可见,从而避免出现复杂的分支语句,并且将状态的变化和状态相关的行为集中在一个类中,更易于理解和维护
  1. 通过命令设计模式将记录日志的行为与业务代码进行解耦,从而提高代码可维护性和可扩展性
  2. 定义一个记录日志的接口 LogCommand
public interface LogCommand {
	//用于执行记录日志的操作
    void execute(String message);
}
  1. 定义两个具体的记录日志的类,分别用于将日志输出到控制台和文件中
@Slf4j
@Component
public class ConsoleLogCommand implements LogCommand {
    @Override
    public void execute(String message) {
        log.info("记录到控制台:{}", message);
    }
}

@Slf4j
@Component
public class FileLogCommand implements LogCommand {
    @Override
    public void execute(String message) {
        try (FileWriter writer = new FileWriter("log.txt", true)) {
            writer.write(message + "\n");
            log.info("记录到文件:{}", message);
        } catch (IOException e) {
            log.error("记录到文件失败:{}", e.getMessage());
        }
    }
}
  1. 定义一个记录日志的 Invoker 类,用于接收命令并执行
@Component
public class LogInvoker {
    private final LogCommand consoleLogCommand;
    private final LogCommand fileLogCommand;

    public LogInvoker(ConsoleLogCommand consoleLogCommand,
                      FileLogCommand fileLogCommand) {
        this.consoleLogCommand = consoleLogCommand;
        this.fileLogCommand = fileLogCommand;
    }

    public void execute(LogCommand command, String message) {
        command.execute(message);
    }
}
  1. 接收请求接口
@RestController
@RequestMapping("/log")
public class LogController {
    private final LogInvoker logInvoker;

    public LogController(LogInvoker logInvoker) {
        this.logInvoker = logInvoker;
    }

    @GetMapping("/console/{message}")
    public String logToConsole(@PathVariable String message) {
        logInvoker.execute(consoleLogCommand, message);
        return "记录到控制台:" + message;
    }

    @GetMapping("/file/{message}")
    public String logToFile(@PathVariable String message) {
        logInvoker.execute(fileLogCommand, message);
        return "记录到文件:" + message;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值