常用设计模式系列(十七)—命令模式

常用设计模式系列(十七)---命令模式

第一节、前言

各位老铁好!

今天我来跟大家分享对象行为型模式第二章节------《命令模式》,"命令"一词,通俗易懂,我们在生活中经常会发出各种各样的命令,就像你告诉你手机上的"siri"、"小爱同学"打开音乐、切歌等等命令,而你的"语音小助理"根据不同的指令完成对不同软件的不同操作,这个过程使用的就是"命令模式"。

第二节、命令模式

命令模式概念:

命令模式(Command Pattern):

将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者或者记录请求日志以及支持可撤销的操作

命令模式的构成:

个人理解

将用户的请求即命令进行抽象,抽象为一个对象,每个客户端调用不同的对象即发起不同的命令来完成指令,命令模式需要支持当请求较多时候的请求队列排队,并且记录请求过程的日志,并且要支持可撤销的动作。

场景举例:

在日常生活中,我们使用开关来控制一些电器的打开或者关闭,例如常见的电灯、排风扇、门禁等等,这个开关可以安装到不同的电器上,那这样对应安装的开关就能控制对应的电器,所以开关与电器没有直接的关系,因为买来的开关可能控制灯泡,可能控制其它电器开关。但是开关与电器之间使用电线连接,开关打开时,电线通电,电器正常工作,相反则电器停止工作,相同的开关可以通过不同的线路来控制不同的电器。这个时候发送者(开关)换个接受者(电器)就可以解耦,发送的对象(开关)只需要知道如何发送请求,而不需要知道如何完成请求(完成请求是电线的职责),这个解耦后运转的模式,称为命令模式。

结构图:

第三节、场景分析及代码实现

场景分析

我们在使用电脑操控的时候,使用的是鼠标去点击系统的某个按钮,按钮上的字展示的不同的功能,然后点击按钮,按钮会根据不同的指令完成某个功能,例如某个系统有主页按钮,登录按钮、退出按钮;

可以分析有如下几个角色

1.功能按钮当做调用者。

2.Command类当做抽象命令。

3.可设计三个具体命令类:主页命令类、登录命令类、退出命令类。

4.可以设计出一个系统类用来当做接收者,包含访问主页命令、登录命令、退出命令

UML图

代码实现

1.调用者功能按钮类FunctionButton

复制代码
package com.yang.command;


/**
 * @ClassName FunctionButton
 * @Description 功能按钮类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class FunctionButton {
    /**
     * 命令
     */
    private Command command;


    public FunctionButton(Command command){
        this.command = command;
    }


    /**
     * 点击动作
     */
    public void click(){
        //执行命令
        command.execute();
    }
}

2.抽象命令类Command

复制代码
package com.yang.command;


/**
 * @ClassName Command
 * @Description 抽象命令类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public interface Command {
    /**
     * 执行命令
     */
    public void execute();
}

3.访问主页命令IndexCommand

复制代码
package com.yang.command;


/**
 * @ClassName IndexCommand
 * @Description 访问主页命令类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class IndexCommand implements Command{
    /**
     * 系统类
     */
    private SystemMenu systemMenu;


    public IndexCommand(){
        systemMenu = new SystemMenu();
    }


    @Override
    public void execute() {
        systemMenu.toIndex();
    }
}

4.登录命令类LoginCommand

复制代码
package com.yang.command;


/**
 * @ClassName LoginCommand
 * @Description 登录命令类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class LoginCommand implements Command{
    /**
     * 系统类
     */
    private SystemMenu systemMenu;


    public LoginCommand(){
        systemMenu = new SystemMenu();
    }


    @Override
    public void execute() {
        systemMenu.toLogin();
    }
}

5.退出命令类LogoutCommand

复制代码
package com.yang.command;


/**
 * @ClassName LogOutCommand
 * @Description 退出命令类
 * @Author IT小白架构师之路
 * @Date 2021/1/25 14:51
 * @Version 1.0
 **/
public class LogOutCommand implements Command{
    /**
     * 系统类
     */
    private SystemMenu systemMenu;


    public LogOutCommand(){
        systemMenu = new SystemMenu();
    }


    @Override
    public void execute() {
        systemMenu.toLogOut();
    }
}

6.系统类作为调用者SystemMenu

复制代码
package com.yang.command;


/**
 * @ClassName SystemMenu
 * @Description 系统菜单类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class SystemMenu {
    /**
     * 访问主页
     */
    public void toIndex(){
        System.out.println("当前是主页");
    }


    /**
     * 登录动作
     */
    public void toLogin(){
        System.out.println("登录成功");
    }


    /**
     * 退出动作
     */
    public void toLogOut(){
        System.out.println("您已退出");
    }
}

7.创建客户端进行测试

复制代码
package com.yang.command;


/**
 * @ClassName Client
 * @Description 客户端
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class Client {
    public static void main(String[] args) {
        //要发起访问主页命令
        Command command = new IndexCommand();
        //功能按钮
        FunctionButton functionButton = new FunctionButton(command);
        //点击
        functionButton.click();
        System.out.println("-------------------我是分割线-----------------------");
        //要发起登录命令
        command = new LoginCommand();
        //功能按钮
        functionButton = new FunctionButton(command);
        //点击
        functionButton.click();
        System.out.println("-------------------我是分割线-----------------------");


        //要发起登出命令
        command = new LogOutCommand();
        //功能按钮
        functionButton = new FunctionButton(command);
        //点击
        functionButton.click();
        System.out.println("-------------------我是分割线-----------------------");


    }
}

8.测试结果如下

复制代码
当前是主页
-------------------我是分割线-----------------------
登录成功
-------------------我是分割线-----------------------
您已退出
-------------------我是分割线-----------------------

使用代码实现队列

加队列的目的是,当业务发送方为多个、业务接收方为多个时,可以使用队列存储多个命令对象,不同的命令对象可以对应不同的请求者。增加队列后,可以将命令存放在队列中,一次执行,类似批量执行。

1.创建命令队列类

复制代码
package com.yang.command;


import java.util.ArrayList;


/**
 * @ClassName CommandQueue
 * @Description 命令队列
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class CommandQueue {
    //队列
    private ArrayList<Command> commands = new ArrayList<Command>();
    //添加命令
    public void addCommand(Command command){
        commands.add(command);
    }


    //删除命令
    public void delCommand(Command command){
        commands.remove(command);
    }
    //执行
    public void execute(){
        for (Command command : commands){
            command.execute();
        }
    }
}

2.改造调用者为队列版FunctionButtonQueue

复制代码
package com.yang.command;


/**
 * @ClassName FunctionButtonQueue
 * @Description 调用者队列版
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class FunctionButtonQueue {
    //队列对象
    private CommandQueue commandQueue;


    public FunctionButtonQueue(CommandQueue  commandQueue){
        this.commandQueue = commandQueue;
    }
    //处理
    public void click(){
        commandQueue.execute();
    }
}

3.客户端测试

复制代码
package com.yang.command;


/**
 * @ClassName ClientQueue
 * @Description 队列版测试
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class ClientQueue {
    public static void main(String[] args) {
        CommandQueue commandQueue = new CommandQueue();
        //要发起访问主页命令
        Command command = new IndexCommand();
        commandQueue.addCommand(command);
        //要发起登录命令
        command = new LoginCommand();
        commandQueue.addCommand(command);
        //要发起登出命令
        command = new LogOutCommand();
        commandQueue.addCommand(command);


        //执行
        FunctionButtonQueue functionButtonQueue = new FunctionButtonQueue(commandQueue);
        functionButtonQueue.click();
    }
}

4.执行结果

复制代码
当前是主页
登录成功
您已退出

增加命令缓存来完成撤销

1.增加临时缓存类CommandCache

复制代码
package com.yang.command;


/**
 * @ClassName CommandCache
 * @Description 执行缓存
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class CommandCache {
    private static Command lastCommad;
    private static Command currentCommand;


    public static Command getLastCommad() {
        return lastCommad;
    }


    public static void setLastCommad(Command lastCommad) {
        CommandCache.lastCommad = lastCommad;
    }


    public static Command getCurrentCommand() {
        return currentCommand;
    }


    public static void setCurrentCommand(Command currentCommand) {
        CommandCache.currentCommand = currentCommand;
    }
}

2.优化功能按钮类

复制代码
package com.yang.command;


/**
 * @ClassName FunctionButton
 * @Description 功能按钮类
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class FunctionButton {


    /**
     * 命令
     */
    private Command command;


    public FunctionButton(Command command){
        this.command = command;
    }


    /**
     * 点击动作
     */
    public void click(){
        Command lastCommad = CommandCache.getLastCommad();
        Command currentCommand = CommandCache.getCurrentCommand();
        //都为空则为本次,第一次撤销无意义
        if(null == lastCommad && null == currentCommand){
            CommandCache.setCurrentCommand(command);
            CommandCache.setLastCommad(command);
        }else{
            //你不为空则把上次的最后一次进行执行
            CommandCache.setLastCommad(currentCommand);
            CommandCache.setCurrentCommand(command);
        }
        //执行命令
        command.execute();
    }
    public void revert(){
        System.out.println("--------撤销开始------");
        Command lastCommad = CommandCache.getLastCommad();
        lastCommad.execute();
        System.out.println("--------撤销结束------");
    }
}

3.测试撤销Client

复制代码
package com.yang.command;


/**
 * @ClassName RevertClient
 * @Description 注释
 * @Author IT小白架构师之路
 * @Date 2021/1/25
 * @Version 1.0
 **/
public class RevertClient {
    public static void main(String[] args) {
        //要发起访问主页命令
        Command command = new IndexCommand();
        //功能按钮
        FunctionButton functionButton = new FunctionButton(command);
        //点击
        functionButton.click();
        System.out.println("-------------------我是分割线-----------------------");
        functionButton.revert();
        //要发起登录命令
        command = new LoginCommand();
        //功能按钮
        functionButton = new FunctionButton(command);
        //点击
        functionButton.click();
        System.out.println("-------------------我是分割线-----------------------");
        //撤销
        functionButton.revert();


    }
}

4.测试结果,完成了撤销操作,回到了上一个界面

复制代码
当前是主页
-------------------我是分割线-----------------------
--------撤销开始------
当前是主页
--------撤销结束------
登录成功
-------------------我是分割线-----------------------
--------撤销开始------
当前是主页
--------撤销结束------

第四节

优缺点及适用场景

优点

1.可以降低系统的耦合度,由于请求者和接收者之间不存在直接调用,则请求者与接收者之间做到了完全解耦,相同的请求者也可以做到调用不同的接受者,同样接收者可以给不同的请求者使用,互相独立,故降低了耦合度。

2.新的命令可以更快的增加到系统中,创建新的具体命令类不会影响其他类

3.可以简单的设计出一个队列命令进行批量执行

4.为撤销和恢复提供了一种设计和实现方案

缺点

1.当命令逐渐增加时,系统会增加很多的具体命令类。

2.当命令增多时,开发人员维护成本变高

适用场景

1.系统需要将调用者与接受者进行解耦,不需要两者进行交互的场景下。

2.系统要在不同的时间指定请求,将请求进行排队或者批量执行是。

3.系统需要增加通用的撤销、恢复功能时。

4.系统需要将一组操作指令进行抽象,完成通用设计时。

扫描二维码

关注我吧

IT小白架构师之路