美文网首页
行为型模式——命令模式

行为型模式——命令模式

作者: Doooook | 来源:发表于2020-08-23 19:34 被阅读0次

    一、命令模式定义

    将一个请求封装成一个对象,从而使你可用不同的请求把客户端参数化,对请求排队或者记录请求日志,以及支持可撤销和恢复操作。
    如:计算器的加减以及撤销操作等

    目的

    命令模式能够做到:

    • 提供一个统一的方法来封装命令和其所需要的参数来执行一个动作。
    • 允许处理命令,例如将命令存储在队列中。

    实现

    image.png

    类图中包括以下元素:

    • Command(命令类):这是表示命令封装的抽象类。它声明了执行的抽象方法,该方法应该由所有具体命令实现。
    • ConcreteCommand(具体命令类):这是命令类的实际实现。它必须执行命令并处理与每个具体命令相关的参数。它将命令委托给接收者。
    • Receiver(接收者):这是负责执行与命令关联的操作的类。
    • Invoker(调用者):这是触发命令的类。通常是外部事件,例如用户操作
    • Client(客户端):这是实例化具体命令对象及其接收者的实际类。

    二、命令模式示例

    我们用命令模式来模拟一个餐馆点餐的场景。从顾客点完餐,到餐被做出来,后厨是命令的接收者,服务员手拿无线点餐器是命令的发起者,无线点餐器上有每个餐品的命令按钮。
    AbstractCommand命令抽象类,声明执行命令的方法:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:19
     * @description: 是一个抽象类,类中对需要执行的命令进行声明
     */
    public abstract class AbstractCommand {
        /**
         * 对外公布一个execute方法用来执行命令
         */
        public abstract void execute();
    }
    

    NoodleCommand(做面)和PieCommand(做馅饼)(命令抽象类的实现对象):

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:38
     * @description: 做面的命令
     */
    public class NoodleCommand extends AbstractCommand {
    
        /**
         * 持有真正实现命令的接收者--后厨对象
         */
        private Kitchen kitchen;
    
        public NoodleCommand(Kitchen kitchen) {
            this.kitchen = kitchen;
        }
    
        @Override
        public void execute() {
            kitchen.noodle();
        }
    }
    
    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:40
     * @description:
     */
    public class PieCommand extends AbstractCommand {
    
        /**
         * 持有真正实现命令的接收者--后厨对象
         */
        private Kitchen kitchen;
    
        public PieCommand(Kitchen kitchen) {
            this.kitchen = kitchen;
        }
    
        @Override
        public void execute() {
            kitchen.pie();
        }
    }
    

    Waiter调用者:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:41
     * @description: 餐厅服务人员对象,持有下单操作命令
     */
    @Data
    public class Waiter {
        /**
         * 做面条命令对象
         */
        private AbstractCommand noodleCommand;
        /**
         * 做馅饼命令对象
         */
        private AbstractCommand pieCommand;
    
        /**
         * 下达做面条的命令
         */
        public void noodleCommandExecute() {
            noodleCommand.execute();
        }
    
        /**
         * 下达做馅饼的命令
         */
        public void pieCommandExecute() {
            pieCommand.execute();
        }
    
    }
    

    Kitchen接收者:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:39
     * @description: 后厨类,接收做菜命令,真正的实现做菜功能,在Command模式中充当Receiver
     */
    public class Kitchen {
    
        /**
         * 做面条
         */
        public void noodle(){
            System.out.println("正在做一碗美味的拉面。");
        }
    
        /**
         * 做馅饼
         */
        public void pie(){
            System.out.println("正在做一个香喷喷的馅饼。");
        }
    
    }
    

    测试:

    /**
         * 行为型模式——命令模式模式1
         */
        @Test
        public void testBehaviorCommand1() {
            // 创建后厨对象(执行命令的人)
            Kitchen kitchen = new Kitchen();
            // 创建做面和做馅饼的命令对象,命令需要命令的接受者
            NoodleCommand noodleCommand = new NoodleCommand(kitchen);
            PieCommand pieCommand = new PieCommand(kitchen);
    
            // 创建一个服务员对象(调用命令的人)
            Waiter waiter = new Waiter();
            waiter.setNoodleCommand(noodleCommand);
            waiter.setPieCommand(pieCommand);
    
            // 客人A:服务员您好,我想来碗面
            waiter.noodleCommandExecute();
            // 客人B:服务员您好,我要来个馅饼
            waiter.pieCommandExecute();
            // 客人C:服务员您好,我想来碗面
            waiter.noodleCommandExecute();
        }
    

    测试结果:

    正在做一碗美味的拉面。
    正在做一个香喷喷的馅饼。
    正在做一碗美味的拉面。

    三、命令模式扩展 – 宏命令示例

    宏命令指的是:包含多个命令的命令,是一个命令的组合。
    假设餐馆里来了一伙4个人,坐在了1号桌上,服务员过来招待,4个人都点完单后,服务员才将1号桌的菜单发送给后厨。
    MenuCommand宏命令对象:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:49
     * @description: 菜单对象,是个宏命令对象
     */
    public class MenuCommand extends AbstractCommand {
    
        /**
         * 记录多个命令对象
         */
        private List<AbstractCommand> list = new ArrayList();
    
        /**
         * 点餐,将下单餐品加入到菜单中
         * @param command
         */
        public void addCommand(AbstractCommand command){
            list.add(command);
        }
    
        @Override
        public void execute() {
            for (AbstractCommand command : list) {
                command.execute();
            }
        }
    }
    

    Waiter2调用者:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:41
     * @description: 餐厅服务人员对象,持有下单操作命令
     */
    @Data
    public class Waiter2 {
        /**
         * 持有宏命令对象--菜单
         */
        private MenuCommand menuCommand = new MenuCommand();
    
        /**
         * 顾客点餐
         * @param command
         */
        public void orderDish(AbstractCommand command) {
            menuCommand.addCommand(command);
        }
    
        /**
         * 顾客点餐完毕,这里就是执行菜单这个组合命令
         */
        public void orderOver() {
            menuCommand.execute();
        }
    
    }
    

    测试:

    /**
         * 行为型模式——命令模式模式(宏命令)
         */
        @Test
        public void testBehaviorCommand2() {
            // 创建后厨对象
            Kitchen kitchen = new Kitchen();
    
            // 创建一个服务员对象
            Waiter2 waiter = new Waiter2();
    
            // 创建做面和做馅饼的命令对象
            NoodleCommand noodleCommand = new NoodleCommand(kitchen);
            PieCommand pieCommand = new PieCommand(kitchen);
    
            // 服务员来到1号餐桌:我们有面条和馅饼,请问4位顾客想吃什么
            // 客人A:我要一碗面条
            waiter.orderDish(noodleCommand);
            // 客人B:给我也来一碗面条
            waiter.orderDish(noodleCommand);
            // 客人C:我要一个馅饼加一碗面条
            waiter.orderDish(pieCommand);
            waiter.orderDish(noodleCommand);
            // 客人D:我要一碗面条
            waiter.orderDish(noodleCommand);
    
            // 服务员:总共4碗面条,一个馅饼,4位请稍等。
            waiter.orderOver();
        }
    

    测试结果:

    正在做一碗美味的拉面。
    正在做一碗美味的拉面。
    正在做一个香喷喷的馅饼。
    正在做一碗美味的拉面。
    正在做一碗美味的拉面。

    四、命令模式扩展 – 可撤销和恢复操作示例

    可撤销指的是回到未执行该命令之前的状态(类似于 Ctrl+z),可恢复指的是取消上次的撤销动作(类似于 Ctrl+Shift+z)。
    有两种方式来实现可撤销的操作:1、反操作式,也叫补偿式,就是撤销时,执行相反的动作;2、存储恢复式,就是把操作前的状态记录下来,撤销时,直接恢复到上一状态。

    4.1 反操作式(补偿式)

    考虑一个计算器的例子,计算器有加、减、撤销和恢复功能,我们先用反操作式来示例一下。
    CalculateCommand命令接口,声明执行、撤销操作:

    /**
     * @author: Jay Mitter
     * @date: 2020-08-17 22:19
     * @description: 是一个抽象类,类中对需要执行的命令进行声明
     */
    public interface CalculateCommand {
        /**
         * 执行命令对应的操作
         */
        void execute();
        /**
         * 执行撤销命令对应的操作
         */
        void undo();
    }
    

    AddCommand(加法命令)和SubtractCommand(减法命令)(命令接口的实现对象):

    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:03
     * @description: 加法命令
     */
    public class AddCommand implements CalculateCommand {
    
        /**
         * 持有真正进行运算操作的对象
         */
        private Processor processor;
    
        /**
         * 要加上的数值
         */
        private int number;
    
        public AddCommand(Processor processor, int number) {
            this.processor = processor;
            this.number = number;
        }
    
        @Override
        public void execute() {
            this.processor.add(number);
        }
    
        @Override
        public void undo() {
            this.processor.subtract(number);
        }
    }
    
    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:04
     * @description: 减法命令
     */
    public class SubtractCommand implements CalculateCommand {
    
        /**
         * 持有真正进行运算操作的对象
         */
        private Processor processor;
    
        /**
         * 要减去的数值
         */
        private int number;
    
        public SubtractCommand(Processor processor, int number) {
            this.processor = processor;
            this.number = number;
        }
    
        @Override
        public void execute() {
            this.processor.subtract(number);
        }
    
        @Override
        public void undo() {
            this.processor.add(number);
        }
    }
    

    Processor计算器的处理器(接收者):

    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:04
     * @description: 计算器的处理器,真正实现运算操作
     */
    @Data
    public class Processor {
    
        /**
         * 记录运算结果
         */
        private int result;
    
        /**
         * 加法
         * @param num
         */
        public void add(int num) {
            result += num;
        }
    
        /**
         * 减法
         * @param num
         */
        public void subtract(int num) {
            result -= num;
        }
    
    }
    

    Calculator计算器(调用者):

    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:11
     * @description:
     */
    @Data
    public class Calculator {
    
        /**
         * 操作的命令的记录,撤销时用
         */
        private List<CalculateCommand> undoCommands = new ArrayList<>();
        /**
         * 撤销的命令的记录,恢复时用
         */
        private List<CalculateCommand> redoCommands = new ArrayList<>();
    
        private CalculateCommand addCommand = null;
        private CalculateCommand subtractCommand = null;
    
        /**
         * 执行加法操作
         */
        public void addPressed() {
            this.addCommand.execute();
            // 把操作记录到历史记录里面
            undoCommands.add(this.addCommand);
        }
    
        /**
         * 执行减法操作
         */
        public void subtractPressed(){
            this.subtractCommand.execute();
            // 把操作记录到历史记录里面
            undoCommands.add(this.subtractCommand);
        }
    
        /**
         * 撤销一步操作
         */
        public void undoPressed() {
            if (this.undoCommands.size() > 0) {
                CalculateCommand calculateCommand = this.undoCommands.get(this.undoCommands.size() - 1);
                calculateCommand.undo();
                // 把这个命令记录到恢复的历史记录里面
                this.redoCommands.add(calculateCommand);
                // 把最后一个命令删除掉
                this.undoCommands.remove(calculateCommand);
            } else {
                System.out.println("很抱歉,没有可撤销的命令~");
            }
        }
    
        public void redoPressed() {
            if (this.redoCommands.size() > 0) {
                CalculateCommand calculateCommand = this.redoCommands.get(this.redoCommands.size() - 1);
                calculateCommand.execute();
                // 把命令记录到可撤销的历史记录里面
                this.undoCommands.add(calculateCommand);
                // 把最后一个命令删除掉
                this.redoCommands.remove(calculateCommand);
            } else {
                System.out.println("很抱歉,没有可恢复的命令~");
            }
        }
    
    }
    

    测试:

    /**
         * 行为型模式——命令模式模式(反操作式(补偿式))
         */
        @Test
        public void testBehaviorCommand3() {
            // 创建接收者,就是我们的计算机的处理器
            Processor processor = new Processor();
    
            // 创建计算器对象
            Calculator calculator = new Calculator();
    
            System.out.println("爸爸:小明,过来帮爸爸算算今天赚了多少钱");
            System.out.println("小明:来了,爸爸你说吧");
            System.out.println("爸爸:白菜卖了20块");
            AddCommand addCommand = new AddCommand(processor, 20);
            calculator.setAddCommand(addCommand);
            calculator.addPressed();
            System.out.println("小明:卖白菜的钱:" + processor.getResult());
    
            System.out.println("爸爸:萝卜卖了15块");
            addCommand = new AddCommand(processor, 15);
            calculator.setAddCommand(addCommand);
            calculator.addPressed();
            System.out.println("小明:加上卖萝卜的钱:" + processor.getResult());
    
            System.out.println("买了一包烟,花了5块");
            SubtractCommand subtractCommand = new SubtractCommand(processor, 5);
            calculator.setSubtractCommand(subtractCommand);
            calculator.subtractPressed();
            System.out.println("小明:减去买烟的钱:" + processor.getResult());
    
            System.out.println("爸爸:不对好像算错了,重来");
            calculator.undoPressed();
            System.out.println("小明:撤销一次后:" + processor.getResult());
            calculator.undoPressed();
            System.out.println("小明:撤销两次后:" + processor.getResult());
            calculator.undoPressed();
            System.out.println("小明:撤销三次后:" + processor.getResult());
    
            System.out.println("爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了");
            calculator.redoPressed();
            System.out.println("小明:恢复一次操作:" + processor.getResult());
            calculator.redoPressed();
            System.out.println("小明:恢复两次操作:" + processor.getResult());
        }
    

    测试结果:

    爸爸:小明,过来帮爸爸算算今天赚了多少钱
    小明:来了,爸爸你说吧
    爸爸:白菜卖了20块
    小明:卖白菜的钱:20
    爸爸:萝卜卖了15块
    小明:加上卖萝卜的钱:35
    买了一包烟,花了5块
    小明:减去买烟的钱:30
    爸爸:不对好像算错了,重来
    小明:撤销一次后:35
    小明:撤销两次后:20
    小明:撤销三次后:0
    爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了
    小明:恢复一次操作:20
    小明:恢复两次操作:35

    4.2 存储恢复式

    还是这个计算器,我们来改动一下,让它变成通过存储恢复的方式进行撤销和恢复撤销。我们只需要让每个命令在执行之前,先记住此时的值,恢复时,只需要将这个命令记住的值恢复就好了。
    与前面的AddCommand(加法命令)和SubtractCommand(减法命令)类似,做如下调整:

    1. 定义一个记录之前值的属性previousValue ;
    2. undo方法改为重新set一下计算结果为之前记录的值。
      SaveAddCommand
    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:41
     * @description: 存储恢复式——加法命令
     */
    public class SaveAddCommand implements CalculateCommand {
    
        /**
         * 持有真正进行运算操作的对象
         */
        private Processor processor;
        /**
         * 记录之前的值
         */
        private int previousValue;
        /**
         * 要加上的数值
         */
        private int number;
    
        public SaveAddCommand(Processor processor, int number) {
            this.processor = processor;
            this.number = number;
        }
    
        @Override
        public void execute() {
            this.previousValue = this.processor.getResult();
            this.processor.add(number);
        }
    
        @Override
        public void undo() {
            this.processor.setResult(this.previousValue);
        }
    }
    

    SaveSubtractCommand

    /**
     * @author: Jay Mitter
     * @date: 2020-08-23 18:41
     * @description: 存储恢复式——减法命令
     */
    public class SaveSubtractCommand implements CalculateCommand {
    
        /**
         * 持有真正进行运算操作的对象
         */
        private Processor processor;
        /**
         * 记录之前的值
         */
        private int previousValue;
        /**
         * 要减去的数值
         */
        private int number;
    
        public SaveSubtractCommand(Processor processor, int number) {
            this.processor = processor;
            this.number = number;
        }
    
        @Override
        public void execute() {
            this.previousValue = this.processor.getResult();
            this.processor.subtract(number);
        }
    
        @Override
        public void undo() {
            this.processor.setResult(this.previousValue);
        }
    }
    

    测试:

    /**
         * 行为型模式——命令模式模式(存储恢复式)
         */
        @Test
        public void testBehaviorCommand4() {
            // 创建接收者,就是我们的计算机的处理器
            Processor processor = new Processor();
    
            // 创建计算器对象
            Calculator calculator = new Calculator();
    
            System.out.println("爸爸:小明,过来帮爸爸算算今天赚了多少钱");
            System.out.println("小明:来了,爸爸你说吧");
            System.out.println("爸爸:白菜卖了20块");
            SaveAddCommand addCommand = new SaveAddCommand(processor, 20);
            calculator.setAddCommand(addCommand);
            calculator.addPressed();
            System.out.println("小明:卖白菜的钱:" + processor.getResult());
    
            System.out.println("爸爸:萝卜卖了15块");
            addCommand = new SaveAddCommand(processor, 15);
            calculator.setAddCommand(addCommand);
            calculator.addPressed();
            System.out.println("小明:加上卖萝卜的钱:" + processor.getResult());
    
            System.out.println("买了一包烟,花了5块");
            SaveSubtractCommand subtractCommand = new SaveSubtractCommand(processor, 5);
            calculator.setSubtractCommand(subtractCommand);
            calculator.subtractPressed();
            System.out.println("小明:减去买烟的钱:" + processor.getResult());
    
            System.out.println("爸爸:不对好像算错了,重来");
            calculator.undoPressed();
            System.out.println("小明:撤销一次后:" + processor.getResult());
            calculator.undoPressed();
            System.out.println("小明:撤销两次后:" + processor.getResult());
            calculator.undoPressed();
            System.out.println("小明:撤销三次后:" + processor.getResult());
    
            System.out.println("爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了");
            calculator.redoPressed();
            System.out.println("小明:恢复一次操作:" + processor.getResult());
            calculator.redoPressed();
            System.out.println("小明:恢复两次操作:" + processor.getResult());
        }
    

    可以看到,测试结果一样:

    爸爸:小明,过来帮爸爸算算今天赚了多少钱
    小明:来了,爸爸你说吧
    爸爸:白菜卖了20块
    小明:卖白菜的钱:20
    爸爸:萝卜卖了15块
    小明:加上卖萝卜的钱:35
    买了一包烟,花了5块
    小明:减去买烟的钱:30
    爸爸:不对好像算错了,重来
    小明:撤销一次后:35
    小明:撤销两次后:20
    小明:撤销三次后:0
    爸爸:哈哈~好像白菜和萝卜没算错,烟这个是私房钱买的,别算进去了
    小明:恢复一次操作:20
    小明:恢复两次操作:35

    五、命令模式扩展 – 队列请求

    对命令对象进行排队,组成工作队列,然后依次取出命令对象来执行。通常用多线程或线程池来进行命令队列的处理。

    六、命令模式扩展 – 日志请求

    日志请求,就是把请求保存下来,一般是采用持久化存储的方式。这样,如果在运行请求的过程中,系统崩溃了,系统重新启动时,就可以从保存的历史记录中,获取日志请求,并重新执行。
    Java中实现日志请求,一般就是将对象序列化保存起来,使用时,进行反序列化操作。

    七、命令模式的优点

    • 更松散的耦合 命令模式使得发起命令的对象和真正实现命令的对象完全解耦。
    • 更好的扩展性 由于命令发起对象和实现对象完全解耦,因此我们可以很容易的扩展新的命令,只需要实现新的命令并进行装配,新命令就可以使用了。这个过程中,已有的实现完全不需要变化。
    • 额外的功能 命令模式还可以提供诸如宏命令、队列请求、日志请求等灵活的操作。

    八、命令模式的应用场景

    • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
    • 在需要支持撤销和恢复撤销的地方,如GUI、文本编辑器等。
    • 需要用到日志请求、队列请求的地方。
    • 在需要事物的系统中。命令模式提供了对事物进行建模的方法,命令模式有一个别名就是Transaction。

    参考:https://blog.csdn.net/daidaineteasy/article/details/103863868/

    相关文章

      网友评论

          本文标题:行为型模式——命令模式

          本文链接:https://www.haomeiwen.com/subject/kpscjktx.html