多线程场景设计利器:分离方法的调用和执行——命令模式总结

前言

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

实现思路

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

其实没什么复杂的,就是使用 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 接口,这就是一个命令接口,将对线程启动的请求和具体的执行分离了。实现该接口,也是启动线程推荐的写法

原文地址:https://www.cnblogs.com/kubixuesheng/p/10353809.html

时间: 2024-08-04 19:30:14

多线程场景设计利器:分离方法的调用和执行——命令模式总结的相关文章

C#程序调用CMD执行命令

在windows环境下,命令行程序为cmd.exe,是一个32位的命令行程序,微软Windows系统基于Windows上的命令解释程序,类似于微软的DOS操作系统.输入一些命令,cmd.exe可以执行,比如输入shutdown -s就会在30秒后关机.总之,它非常有用.打开方法:开始-所有程序-附件 或 开始-寻找-输入:cmd/cmd.exe 回车.它也可以执行BAT文件. 下面介绍使用C#程序调用cmd执行命令: 代码: 1 using System; 2 using System.Coll

C# 调用CMD执行命令行

这几天用c#做了一个项目,其中一个功能是要把生成的临时文件隐藏,同时,不能在屏幕上有调用CMD的痕迹,这里生成的临时文件的绝对路径为delfile为文件的绝对路径, 代码如下: private void HiddenFile() { System.Diagnostics.Process proRestart = new System.Diagnostics.Process();    //创新Process proRestart.StartInfo.WindowStyle = System.Di

C#程序调用cmd执行命令(转)

C#通过程序来调用cmd命令的操作 string str = Console.ReadLine(); System.Diagnostics.Process p = new System.Diagnostics.Process(); p.StartInfo.FileName = "cmd.exe"; p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动 p.StartInfo.RedirectStandardInput = true

C#程序调用CMD执行命令方法

先将adb.exe环境加入系统环境变量 { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; //process.StartInfo.Arguments = "adb deviecs"; process.StartInfo.WorkingDirectory = "C:/Users/Administrator"; process.StartInfo.Us

linux 程序调用system执行命令

正确使用system方法,判断返回值 int exeCmd(const char *cmd) { pid_t status; status = system(cmd); if (-1 == status) { WriteLog("system error!"); } else { WriteLog("exit status value = [0x%x]\n", status); if (WIFEXITED(status)) { if (0 == WEXITSTATU

C#程序调用cmd执行命令-MySql备份还原

1.简单实例 //备份还原mysql public static void TestOne() { Process p = new Process(); p.StartInfo.FileName = "cmd.exe"; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.

方法的调用

循环的嵌套: 循环体本身又是另一个循环的循环,就是循环的嵌套. 外面的循环称为外层循环,里边的循环称为内层循环: //外层循环 for(){ //内层循环 for(){ } } 外层循环的每次执行,内层循环都循环多次!! 循环变量一般都使用i,j,k,m等字符. 循环嵌套的几种格式: for(){ for(){ } } for(){ while(){ } } while(){ for(){ } } while(){ while(){ } } break 和 continue关键字: break用

(十三)WebGIS中工具栏的设计之命令模式

1.背景 从这一章节开始我们将正式进入WebGIS的工具栏中相关功能的设计和实现.我们以ArcMap中的工具栏中的基本工具为模板,将其中的放大.缩小.平移.全图.清除.定位.I查询.距离量测.面积量测在WebGIS中进行实现. 这里,我先跟大家说一个基本的概念.我们一般将工具分为Command和Tool.所谓command是指该工具被调用后,生效一次即终止.而Tool则是当被调用后,持续有效,直至终止该工具或者切换工具. 按照这个理论,我们可以将工具栏中的基本工具分一下类: Command:全图

命令模式在Android实际场景中运用

命令模式(Command Pattern),是行为型模式之一.在日常生活和开发过程中,这是一个非常常见的设计模式,比如我们电脑关机这个操作,我们只需要点击关机键便可以实现关机,内部是通过什么进行关机的,我们不需要去知道,计算机会帮我们实现这个功能. 优点: (1)比较容易地实现一个命令队列. (2)比较容易将队列记入日志. (3)请求者和实现者通过接口进一步解耦. (4)可以容易地实现对请求的撤销和恢复. (5)加入新的命令不影响其它类的操作. 引用上述关机的例子,对名词进行一些解释: ·Cli