欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

命令模式(十一)

程序员文章站 2022-05-26 08:42:35
...

一、定义

    将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求的日志,可以提供命令的撤销和恢复功能。

    简单的说,命令模式是实现的 “行为请求者(Invoker)” 与 “行为实现者(Receiver)” 间的解耦,将请求封装成一个命令,调用者只需要执行相应的 “命令(Command)” 即可。


二、类图和成员角色

命令模式(十一)

1. Receiver 接受者

    该角色就是具体的干活者,命令传递到这里就是被执行的。

2. Command 命令

    通常该角色只是定义一个命令的抽象(接口),声明执行的方法。

3. ConcreteCommand 具体命令

    命令接口的实现,通常代表一个具体的命令,例如 “开机” 命令。

    通常我们会将 Receiver 对象封装在 Command 中,这样调用命令的 “执行” 方法时,就可以使用 Receiver 接收者去具体实现了。

4. Invoker 调用者

    接收并执行命令,通常可以持有一个或多个 Command 命令。


三、优缺点

优点:

    1.解耦。调用者 invoker 和接收者 receiver 之间不再存在依赖关系。调用者只需要执行相应的命令 command,无需关心是具体哪个接收者执行。

    2.可扩展。Command 的子类非常容易扩展。

    3.命令模式结合其它设计模式会更优秀。命令模式可以结合责任链模式, 实现命令族解析任务; 结合模板方法模式, 则可以减少 Command 子类的膨胀问题。

缺点:

    如果有N个命令, 问题就出来了, Command的子类就可不是几个, 而是N个, 这个类膨胀得非常大, 这个就需要读者在项目中慎重考虑使用。


四、实例

    假设有一个手机,手机会接收到一系列用户命令。

1.定义手机类,它是一个接收者
public interface Receiver {
}
 
public class Mobile implements Receiver {
     
    public void powerOn() {
        System.out.println("开机");
    }
    public void powerOff() {
        System.out.println("关机");
    }
    public void alarm() {
        System.out.println("闹铃");
    }
 
}
2.定义一个抽象的命令类 Command 
public abstract class Command {
 
    protected Mobile mMobile = new Mobile();
 
    public abstract void execute(); // 子类需要完成的方法
 
}
3.定义一些具体命令类
public class PowerOnCommand extends Command {
    @Override
    public void execute() {
        mMobile.powerOn();
    }
}
 
public class PowerOffCommand extends Command {
    @Override
    public void execute() {
        mMobile.powerOff();
    }
}
 
public class AlarmCommand extends Command {
    @Override
    public void execute() {
        mMobile.powerOn(); // 先开机再闹铃
        mMobile.alarm();
    }
}
4.定义一个调用者
public class Invoker {
 
    private Command mCammand;
 
    public void setCommand(Command command) {
        this.mCammand = command;
    }
 
    // 执行命令
    public void action() {
        this.mCammand.execute();
    }
}
5.模拟一个场景
public class Main {
 
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        invoker.setCommand(new PowerOnCommand());
        invoker.action();
        invoker.setCommand(new PowerOffCommand());
        invoker.action();
        invoker.setCommand(new AlarmCommand());
        invoker.action();
    }
}

    通过这个小例子,我们可以简单的看到命令模式是怎么工作的,它很好的解耦了调用者和接收者的联系。也许这个例子你还看不到命令模式的好处。

    但是当你的场景变得越来越大,接收者越发复杂的时候,不使用命令模式,也许你的调用者(上层逻辑)需要处理很多额外的逻辑,例如创建指定的接收者,分别执行这些接收者的详细方法,这样上次逻辑就变得很臃肿。但是使用命令模式的话,这些复杂的逻辑都可以在具体的每个命令中处理和执行,上次只需要调用简单的一条命令即可,也不用关心具体的接收者如何执行,这样上层逻辑就变得很清晰和简单了。


五、继续扩展

1.命令队列

    有时候命令不再是单一的一条命令,而是一系列命令,这样就形成了一个命令队列。命令队列的实现有很多种方式,例如我们可以增加一个“队列”来管理命令。

import java.util.ArrayList;
 
/**
 * 命令队列本身也可以是一个命令,也有 execute() 方法
 */
public class CommandQueue extends Command {
 
    // 使用一个 list 来存储多条命令
    private ArrayList<Command> mCommandQueue = new ArrayList<>();
 
    public void addCommand(Command command) {
        mCommandQueue.add(command);
    }
 
    public void removeCommand(Command command) {
        mCommandQueue.remove(command);
    }
 
    @Override
    public void execute() {
        for (Command command : mCommandQueue) {
            command.execute();
        }
    }
}
2.撤销

    用户发出命令,要撤回,怎么办?就类似我们使用 Ctrl+Z 组合键(undo 功能)。

    通常有两种方式:

        一是结合备忘录模式还原最后状态,该方法适合接收者为状态变更的情况;

        二是通过新增一个命令,实现事件回滚,撤销命令也是一个命令,例如计算机做加法的时候,回滚命令通常就可以设定为加一个相反数实现。

3.记录请求日志

    请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。

    我们可以将 Command 类实现序列化接口,即实现 Serializable 接口。然后在每次执行命令的时候,将该命令写到日志文件中去。


查看更多:设计模式分类以及六大设计原则

相关标签: 命令模式