设计模式之命令模式

文章目录


定义

命令模式也称作动作模式,属于行为型模式的一种,书中的定义:将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作。
本质上看,就是将要执行的任务参数化
具体做法就是将命令封装为参数,并且由命令去执行底层真实的业务逻辑。客户端需要做的事情是,创建命令,将命令发送至调用者,再由调用者去执行命令。

可以理解为增加了一个中间层,这个中间层就是命令参数,实现了调用者和执行者之间的解耦,换句话说,执行者不再需要调用者直接去调用执行具体的业务逻辑,而是通过命令的方式告诉执行者,具体需要执行的任务。而在这个命令执行过程中,自然就有了更多的机会去控制不同命令的执行一些额外的操作,比如定义中说的对请求排队,记录日志,撤销等操作。

以一个餐厅示例说明,张三带着别人的女朋友去吃印度特色美食,如果没有使用命令模式的效果就是,张三直接向厨房的厨师点餐,厨师是执行者,张三向厨师菜单,厨师制作。假设客流量比较大,菜品比较多,厨师就要一边制作菜品,一边忙着管理菜单。如果使用命令模式就相当于增加了服务员或者点餐台这个角色,张三向点菜台点餐,具体后续的操作由服务员去向厨师发送命令。而在这个过程中,就可以进行一些额外的操作,比如对按照先来后进行排序,或者某个菜品售罄的情况下可以直接告知张三,并且也可以对一天的营业额,客流量、利润等等信息进行记录统计。

再比如,在操作系统的世界中,上层应用中直接调用内核接口是绝对不被允许的,于是就搞了一个外壳,通过这个外壳shell去调用,内核的任务很单一,仅仅是按照自己的执行逻辑去执行任务,具体内核是怎么操作的,上层用户无需知道,在这个过程中也可以进行各种其他的操作,比如权限的验证,操作日志记录,拦截非法操作等。

命令模式中总体其实就分为三种角色:命令接收者、命令调用者以及命令,过程就是命令调用者享命令接收者发送命令

  • 抽象命令角色(Command):命令的抽象,负责当前的命令的调用,比如包含一个execute方法
  • 具体命令角色(Concrete command):命令的具体实现类,应该持有命令接收者,将命令发送至接收者
  • 接收者角色(Ceceiver):接收者,负责接收命令,进行具体的执行逻辑,注意,这里是接收者,而不是执行者,具体的执行逻辑实现是自由的
  • 调用者角色(Invoker):持有命令,负责调用命令,比如调用execute方法

UML:
在这里插入图片描述
图片来源:百度图库

2.示例

以一个服务器执行操作指令为示例,对命令模式进行一个基本实现,还是一个观点,设计模式只是思想,以下示例只是一个非常简单的例子,目的仅仅是对思想进行说明,具体实现应充分考虑实际情况,做出更优的设计。

定义抽象命令角色,接口或抽象类,命令角色应该持有接收者的实例,并包含一个实际的执行命令的方法

/**
 * @description: 命令抽象
 * @version: 1.0
 */
public abstract class Command {

    protected List<Server> servers;

    public Command(List<Server> servers) {
        this.servers = servers;
    }

    /**
     * 命令执行方法
     */
    abstract void execute();
}

具体的命令实现类,以下实现了三个命令参数,模拟查看进程命令、 统计网络状态命令、以及关闭机器命令
PSCommand:

/**
 * @description: 查看系统进程命令
 * @version: 1.0
 */
public class PSCommand extends Command{


    public PSCommand(List<Server> servers) {
        super(servers);
    }

    @Override
    public void execute() {
        for (Server server : servers){
            System.out.println("检查系统状态,查询系统进程");
            server.processShow();
        }
    }
}

NetCommand :

/**
 * @description: 统计网络状态命令
 * @version: 1.0
 */
public class NetCommand extends Command{

    public NetCommand(List<Server> servers) {
        super(servers);
    }

    @Override
    public void execute() {
        for (Server server : servers){
            System.out.println("检查系统状态,查询网络状态");
            server.netStat();
        }
    }
}

ShutdownCommand :

/**
 * @description: 关机指令
 * @version: 1.0
 */
public class ShutdownCommand extends Command{


    public ShutdownCommand(List<Server> servers) {
        super(servers);
    }

    @Override
    public void execute() {
        for (Server server : servers){
            System.out.println("检查系统状态,准备关闭机器");
            server.shutdown();;
        }
    }
}

接下来是实现命令接收者,在这个示例中大概就是服务器实例,命令接收者的工作是接收具体的命令,示例中体现为等待命令的主动调用,当一个命令执行就会调用命令对应的接收方法,而接收者应该对命令参数对应的操作进行具体的实现,以上的实现方式当然是自由的,只要遵循命令模式的思想即可。

这里顺便对做了一个抽象,以便可以扩展其他类型的服务器实例。
Server :

/**
 * @description: 服务器 (命令接收者)
 * @version: 1.0
 */
public abstract class Server {

    protected String ip;

    public Server(String ip) {
        this.ip = ip;
    }

    public abstract void processShow();

    public abstract void netStat();

    public abstract void shutdown();
}

ApplicationServer :

/**
 * @description: 服务器(命令接受者)
 * @version: 1.0
 */
public class ApplicationServer extends Server{


    public ApplicationServer(String ip) {
        super(ip);
    }

    @Override
    public void processShow() {
        System.out.println(this.ip + "=>查看系统进程");
    }

    @Override
    public void netStat() {
        System.out.println(this.ip + "=>查看网络状态");
    }

    @Override
    public void shutdown() {
        System.out.println(this.ip + "=>执行关机操作");
    }
}

剩下的就是命令的调用者,命令的调用者主要职责就是创建命令,调用命令,命令调用者应持有命令实例,示例中体现为在调用者中维护一个命令数组,并对外提供构造的方式初始化命令。除此之外,该角色应该实现具体的命令调用逻辑,实例中只是简单的遍历命令数组,执行命令的execute方法。实际业务中实现依旧是自由的,只要遵循思想本质即可。

/**
 * @description: 命令调用者 invoke
 * @version: 1.0
 */
public class ServerInvoker {
    List<Command> commands = new ArrayList<>();

    public void addCommand(Command command){
        commands.add(command);
    }

    //调用命令执行
    public void invoke(){
        for (Command command : commands){
            command.execute();
        }
    }
}

测试:


/**
 * @description: test
 * @version: 1.0
 */
public class Test {
    public static void main(String[] args) {

        //创建server实例
        List<Server> servers = new ArrayList<>();
        servers.add(new ApplicationServer("192.168.1.13"));
        servers.add(new ApplicationServer("192.168.1.14"));
        servers.add(new ApplicationServer("192.168.1.15"));

        //调用者 & 添加命令
        ServerInvoker invoker  = new ServerInvoker();
        invoker.addCommand(new PSCommand(servers));
        invoker.addCommand(new NetCommand(servers));
        invoker.addCommand(new ShutdownCommand(servers));

        //执行命令
        invoker.invoke();
    }
}

input:

检查系统状态,查询系统进程
192.168.1.13=>查看系统进程
检查系统状态,查询系统进程
192.168.1.14=>查看系统进程
检查系统状态,查询系统进程
192.168.1.15=>查看系统进程
检查系统状态,查询网络状态
192.168.1.13=>查看网络状态
检查系统状态,查询网络状态
192.168.1.14=>查看网络状态
检查系统状态,查询网络状态
192.168.1.15=>查看网络状态
检查系统状态,准备关闭机器
192.168.1.13=>执行关机操作
检查系统状态,准备关闭机器
192.168.1.14=>执行关机操作
检查系统状态,准备关闭机器
192.168.1.15=>执行关机操作
3.总结

一句话概括命令模式就是将命令调用者和接收者分离,调用者只需要调用命令,不关心是谁接收,怎么执行,而接收者只是安静的等待命令的到来,命令是谁发的,何时发的,怎么发的,接收者都不知道,双方不需要感知对方的存在,他们之间的纽带就是命令。调用者只需要知道“我要执行什么命令”,而接收者只知道对于一个命令“我要干什么”,命令就是他们之间的“请求参数”。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值