zoukankan      html  css  js  c++  java
  • 多线程场景设计利器:分离方法的调用和执行——命令模式总结

    前言

    个人感觉,该模式主要还是在多线程程序的设计中比较常用,尤其是一些异步任务执行的过程。但是本文还是打算先在单线程程序里总结它的用法,至于多线程环境中命令模式的用法,还是想在多线程的设计模式里重点总结。

    实现思路

    其实思路很简单,就是把方法的请求调用和具体执行过程分开,让客户端不知道该请求是如何、何时执行的。那么如何分开呢?

    其实没什么复杂的,就是使用 OO 思想,把对方法的请求封装为对象即可,然后在设计一个请求的接受者对象,当然还要有一个请求的发送者对象,请求本身也是一个对象。最后,请求要如何执行呢?

    故,除了请求对象,请求发送者,请求接受者,还要一个请求执行者——这里可以看成是客户端,而请求(其实叫命令、或者请求都是一样的意思,后文就用请求这个术语)最好设计为抽象的(或者接口)。

    也可得知,命令模式是对象的行为型的设计模式。

    简单的命令模式

    模拟场景:在线教育平台售卖一些培训的视频课程,规定必须付费后才能观看,故管理员需要有开放课程观看和关闭课程观看权限的操作

    首先需要一个抽象的命令(请求)接口

    public interface ICommand { // 抽象的命令(请求)接口
        void execute();
    }

    然后设计一个课程类——Lesson,它代表课程本身,也是命令(请求)的接受者,因为是对课程这个实体下命令

    public class Lesson { // 代表课程本身,也是命令(请求)的接受者,因为是对课程这个实体下命令
        private String name;
    
        public Lesson(String name) {
            this.name = name;
        }
    
        public void openLesson() {
            System.out.println("可以观看课程:" + name);
        }
    
        public void closeLesson() {
            System.out.println("不可以观看课程:" + name);
        }
    }

    下面是两个具体的命令类,分别实现命令接口,里面是有聚合关系,把课程 Lesson 的引用聚合到命令类,哪一个命令要对哪一个实体,不能写错,比如关闭对关闭。

    public class CloseCommand implements ICommand {
        private Lesson lesson;
    
        public CloseCommand(Lesson lesson) {
            this.lesson = lesson;
        }
    
        @Override
        public void execute() {
            this.lesson.closeLesson();
        }
    }
    //////////////////////////////////////////////////////
    public class OpenCommand implements ICommand {
        private Lesson lesson;
    
        public OpenCommand(Lesson lesson) {
            this.lesson = lesson;
        }
    
        @Override
        public void execute() {
            this.lesson.openLesson();
        }
    }

    设计一个管理员类,作为命令(请求)的调用者,用来发出请求(命令),而命令的实际执行,交给了命令(请求)的接受者——Lesson

    public class Admin2 {
        private ICommand commond;
    
        public void setCommond(ICommand commond) {
            this.commond = commond;
        }
    
        public void executeCommond() {
            this.commond.execute();
        }
    }

    客户端

            Lesson lesson1 = new Lesson("c++"); // 请求(命令)的接受者
            CloseCommand closeCommand1 = new CloseCommand(lesson1); // 命令封装为对象
            OpenCommand openCommand1 = new OpenCommand(lesson1);
            Admin2 admin2 = new Admin2(); // 请求(命令)的调用者:用来发出请求
            admin2.setCommond(openCommand1); // 将命令传给调用者
            admin2.executeCommond(); // 发出请求(命令),但是admin 并不知道这个请求(命令)发给了谁,是谁在执行这个请求(命令)
            admin2.setCommond(closeCommand1);
            admin2.executeCommond();

    如上就实现了请求调用和具体执行的分离(解耦)

    一次执行多个命令

    下面是一次执行多个命令的写法,也可以作为宏命令的实现

    命令接口和具体命令都不变,admin 变化如下:

    public class Admin {
        private List<ICommand> commondList = new ArrayList<>(); // 使用 ArrayList 还能保证命令的顺序执行
    
        public void addCommond(ICommand commond) {
            commondList.add(commond);
        }
    
        public void executeCommond() {
            for (ICommand commond : commondList) {
                commond.execute();
            }
            commondList.clear();
        }
    }

    当然这里用栈等数据结构去包装命令也是可以的

            Lesson lesson = new Lesson("java"); // 请求(命令)的接受者
            CloseCommand closeCommand = new CloseCommand(lesson); // 命令
            OpenCommand openCommand = new OpenCommand(lesson);
            Admin admin = new Admin(); // 请求(命令)的调用者:用来发出请求
            admin.addCommond(openCommand); // 将命令传给调用者
            admin.addCommond(closeCommand);
            admin.executeCommond();

    引申:空类型模式

    再比如,使用静态数组去包装命令,这里引申一个空类型模式,就是说有一个类,这个类什么都不做,就是占位或者初始化用的,代替 null 类型。

    下面举一个例子,设计一个控制器,控制电灯的开关,闪烁,变暗,变亮等操作

    public interface ICommand2 {
        void execute(); // 命令接口
    }
    //////////////////////////////////
    public class LightOffCommand implements ICommand2 {
        private Light light;
    
        public LightOffCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            this.light.off();
        }
    }
    //////////////////////////////////
    public class LightOnCommand implements ICommand2 {
        private Light light;
    
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            this.light.on();
            this.light.zoomin();
            this.light.blink();
        }
    }
    //////////////////////////////////
    public class EmptyCommand implements ICommand2 { // 空类型模式的体现
        @Override
        public void execute() {
            System.out.println("什么都不做");
        }
    }
    //////////////////////////////////
    public class Light {
        public Light() {
        }
    
        public void on() {
            System.out.println("电灯打开");
        }
    
        public void off() {
            System.out.println("电灯关闭");
        }
    
        public void zoomin() {
            System.out.println("灯光变强");
        }
    
        public void zoomout() {
            System.out.println("灯光变弱");
        }
    
        public void blink() {
            System.out.println("灯光闪烁");
        }
    
        public void noBlink() {
            System.out.println("灯光停止闪烁");
        }
    }

    下面是一个控制器类,setCommand 方法可以设置某个命令和某个操作的对应关系,初始化时,使用空类型模式

    public class MainController {
        private ICommand2[] onCommands;
        private ICommand2[] offCommands;
    
        public MainController() {
            this.onCommands = new ICommand2[3];
            this.offCommands = new ICommand2[2];
            ICommand2 emptyCommand = new EmptyCommand();
            for (int i = 0; i < 3; i++) {
                this.onCommands[i] = emptyCommand;
            }
            for (int i = 0; i < 2; i++) {
                this.offCommands[i] = emptyCommand;
            }
        }
    
        public void setCommand(int idx, ICommand2 onCommand, ICommand2 offCommand) {
            this.onCommands[idx] = onCommand;
            this.offCommands[idx] = offCommand;
        }
    
        public void executeOnCommand(int idx) {
            this.onCommands[idx].execute();
        }
    
        public void executeOffCommand(int idx) {
            this.offCommands[idx].execute();
        }
    }

    客户端

            MainController mainController = new MainController();
            Light roomLight = new Light();
            Light doorLight = new Light();
            LightOnCommand roomLightOnCommand = new LightOnCommand(roomLight);
            LightOffCommand roomLightOffCommand = new LightOffCommand(roomLight);
            LightOnCommand doorLightOnCommand = new LightOnCommand(doorLight);
            LightOffCommand doorLightOffCommand = new LightOffCommand(doorLight);
    
            mainController.setCommand(0, roomLightOnCommand, roomLightOffCommand);
            mainController.setCommand(1, doorLightOnCommand, doorLightOffCommand);
    
            mainController.executeOnCommand(0);
            mainController.executeOffCommand(0);
            mainController.executeOnCommand(1);
            mainController.executeOffCommand(1);
            mainController.executeOnCommand(2);

    命令模式在单线程环境下的优点(使用场景)

    通过封装对方法的请求调用和方法执行过程,并将其分离,也就是所谓的完全解耦了。

    故可以对方法的调用执行实现一些额外操作,比如记录日志,撤销某个方法的请求调用,或者实现一次请求,N 次执行某个方法等。

    在架构上,可以让程序易于扩展新的请求(命令)。

    命令模式在多线程程序中的优点

    这样做,在多线程环境下的好处是:

    1、避免算法(策略)模块执行缓慢拖累调用方——抽象了需要等待的操作

    2、控制执行顺序,因为请求调用和具体执行分离,故执行顺序和调用顺序没有关系

    3、可以轻松实现请求的取消,或者反复执行某个请求

    4、请求调用和具体执行分离后,进一步把负责调用的机器和负责执行的机器分开,可以基于网络,实现分布式程序

    命令的撤销实现

    前面,无论在什么环境下,都提到了能撤销命令(请求),故命令模式经常和备忘录模式搭配使用。参考:保存快照和撤销功能的实现方案——备忘录模式总结

    这里举一个很简单的例子,还是电灯开关的例子

    public interface ICommand3 {
        void execute();
        void undo(); // 和 execute 执行相反的操作
    }
    //////////////////////////////////
    public class EmptyCommand implements ICommand3 {
        @Override
        public void execute() {
            System.out.println("什么都不做");
        }
    
        @Override
        public void undo() {
            System.out.println("什么都不做");
        }
    }
    /////////////////////////////////
    public class LightOnCommand implements ICommand3 {
        private Light light;
    
        public LightOnCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            this.light.on();
            this.light.zoomin();
            this.light.blink();
        }
    
        @Override
        public void undo() {
            this.light.noBlink();
            this.light.zoomout();
            this.light.off();
        }
    }
    ///////////////////////////////////
    public class LightOffCommand implements ICommand3 {
        private Light light;
    
        public LightOffCommand(Light light) {
            this.light = light;
        }
    
        @Override
        public void execute() {
            this.light.off();
        }
    
        @Override
        public void undo() {
            this.light.on();
        }
    }

    控制器也要变化,初始化命令的同时,也要初始化 undo 命令

    public class MainController {
        private ICommand3[] onCommands;
        private ICommand3[] offCommands;
        private ICommand3 undoCommand; // 记录上一个命令
    
        public MainController() {
            this.onCommands = new ICommand3[3];
            this.offCommands = new ICommand3[2];
            ICommand3 emptyCommand = new EmptyCommand();
            for (int i = 0; i < 3; i++) {
                this.onCommands[i] = emptyCommand;
            }
            for (int i = 0; i < 2; i++) {
                this.offCommands[i] = emptyCommand;
            }
            this.undoCommand = emptyCommand; // 初始化 undo 命令
        }
    
        public void setCommand(int idx, ICommand3 onCommand, ICommand3 offCommand) {
            this.onCommands[idx] = onCommand;
            this.offCommands[idx] = offCommand;
        }
    
        public void executeOnCommand(int idx) {
            this.onCommands[idx].execute();
            this.undoCommand = this.onCommands[idx];
        }
    
        public void executeOffCommand(int idx) {
            this.offCommands[idx].execute();
            this.undoCommand = this.offCommands[idx];
        }
    
        public void undoCommand() {
            this.undoCommand.undo();
        }
    }

    客户端

            MainController mainController = new MainController();
            Light roomLight = new Light();
            LightOffCommand offCommand = new LightOffCommand(roomLight);
            LightOnCommand onCommand = new LightOnCommand(roomLight);
            mainController.setCommand(0, onCommand, offCommand);
            mainController.executeOnCommand(0);
            System.out.println();
            mainController.executeOffCommand(0);
            System.out.println();
            mainController.undoCommand();
            System.out.println();
            mainController.executeOffCommand(0);
            System.out.println();
            mainController.executeOnCommand(0);
            System.out.println();
            mainController.undoCommand();

    打印如下

    电灯打开
    灯光变强
    灯光闪烁
    
    电灯关闭
    
    电灯打开
    
    电灯关闭
    
    电灯打开
    灯光变强
    灯光闪烁
    
    灯光停止闪烁
    灯光变弱
    电灯关闭

    命令模式的缺陷

    个人觉得,唯一的缺点就是会使得程序复杂性提高,但是我认为微不足道,基础扎实的 RD 应该无压力阅读和使用才对,因为在多线程程序里,该模式大量出现,比如 Netty 等框架就大量使用了该思想。 

    命令模式和策略模式的区别 

    策略是不同的算法做同一件事情。不同的策略之间可以相互替换。比如实现一个支付功能,有微信支付,支付宝支付,各自渠道的支付。。。

    命令是不同的命令做不同的事情。对外隐藏了具体的执行细节。比如菜单中的复制,移动和压缩

    JDK 中的命令模式

    最最常见的就是 lang 包里的 Runnable 接口,这就是一个命令接口,将对线程启动的请求和具体的执行分离了。实现该接口,也是启动线程推荐的写法

    欢迎关注

    dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

  • 相关阅读:
    NOIp2016 D2T3 愤怒的小鸟【搜索】(网上题解正解是状压)
    NOIp2018D1T1 积木大赛 【思维】
    NOIp2018D1T2 货币系统【分析&完全背包】
    NOIp2017D1T2 时间复杂度【模拟】
    NOIp2015D1T3 斗地主【暴搜】
    NOIp2013D2T3 华容道【搜索&图论-最短路】
    Andrew算法求二维凸包-学习笔记
    最小割的一些小技巧(实用小干货)
    USACO4.3 Buy Low, Buy Lower【简单dp·高精度】
    iOS本地推送与远程推送详解
  • 原文地址:https://www.cnblogs.com/kubixuesheng/p/10353809.html
Copyright © 2011-2022 走看看