天天看点

一文带你理解命令模式

命令模式是一种行为型设计模式,强调的是封装性,这点跟外观模式有点相似。但是因为他没有既定的强制规则,所以更加灵活多变。

定义

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

场景

  1. 需要抽象出待执行的动作
  2. 在不同的时刻需要指定、排列和执行不同的请求
  3. 某些需要支持撤销、日志记录额、事务操作的场景

角色

接收者

负责具体实施或执行一个请求,在接收者类中封装的方法称为行动方法

命令抽象

定义所有命令类的抽象接口和执行方法

命令实现

命令抽象的实现者,实现执行方法,调用接收者的行动方法,命令实现和接收者有弱耦合关系

请求者

调用命令对象执行具体的请求,一般是一个方法,也叫请求者

说明

命令模式的特色是优秀的扩展性,在整个流程中,我们可以通过构造不同的请求者来发出具体的命令从而调用接收者指定的行动方法,相当于通过命令这个角色,建立了请求者和接收者的关系,并且两者不具备耦合关系。

代码演示

计算机的关机休眠过程是由一系列的命令来组成的。从点击关机按钮开始(也就是发起一个关机请求),系统会产生一个关机的信号(命令)给计算机系统,系统接收到指令后,就进行关机操作。这个过程三个角色很清晰。我们可以按照命令模式进行实现。

首先创建我们的接收者,也就是具体逻辑的定义者,这里我们定义主板类

/**
 * 接收者角色,执行具体的逻辑
 */
public class MainBoard {

  /**
   * 接收者行动方法1
   */
  public void shutdown() {
    System.out.println("准备关机...");
  }

  /**
   * 接收者行动方法2
   */
  public void sleep() {
    System.out.println("准备休眠...");
  }
}

           

然后我们创建抽象命令接口和具体的命令。我们这里创建两个命令类,一个关机操作,一个休眠操作。

/**
 * 抽象命令
 */
public interface ICommand {

  /**
   * 抽象执行方法
   */
  void execute();
}



/**
 * 命令实现着:关机命令
 */
public class ShutdownCommand implements ICommand {

  private MainBoard mReceiver;

  public ShutdownCommand(MainBoard receiver) {
    this.mReceiver = receiver;
  }

  @Override public void execute() {
    mReceiver.shutdown();
  }
}



/**
 * 命令实现者:休眠命令
 */
public class SleepCommand implements ICommand {

  private MainBoard mReceiver;

  public SleepCommand(MainBoard receiver) {
    this.mReceiver = receiver;
  }

  @Override public void execute() {
    mReceiver.sleep();
  }
}
           

同样的,我们创建2个请求角色类

/**
 * 请求者角色;关机请求
 */
public class ShutDownInvoker {

  private ICommand command;

  public ShutDownInvoker(ICommand command) {
    this.command = command;
  }

  public void shutDownComputer() {
    System.out.println("请求关闭计算机...");
    this.command.execute();
  }

}

/**
 * 请求者角色:休眠请求
 */
public class SleepInvoker {

  private ICommand command;

  public SleepInvoker(ICommand command) {
    this.command = command;
  }

  public void sleepComputer() {
    System.out.println("请求休眠计算机...");
    this.command.execute();
  }
}

           

我们来看下调用,发起一个请求。

/**
   * 模拟客户端调用
   */
  public static void main(String[] args) {
    // 首先要创建接收者
    MainBoard mb = new MainBoard();
    
    // 关机流程
    System.out.println("---关机流程---");
    ShutdownCommand shutdownCommand = new ShutdownCommand(mb);
    ShutDownInvoker shutDownInvoker = new ShutDownInvoker(shutdownCommand);
    shutDownInvoker.shutDownComputer();

    // 休眠流程
    System.out.println("---关机流程---");
    SleepCommand sleepCommand = new SleepCommand(mb);
    SleepInvoker sleepInvoker = new SleepInvoker(sleepCommand);
    sleepInvoker.sleepComputer();
  }
           

看下输出

---关机流程---
请求关闭计算机...
准备关机...
---关机流程---
请求休眠计算机...
准备休眠...
           

可以看到,一个请求会按照:发出请求–产生命令–接收并处理 的流程进行。引入命令着对象,这也是命令模式实现请求者和接收者解耦的方式。

关于源码

在安卓源码中,页面的启动,也就是

Activity

的启动可以看到命令模式的影子。

首先是

Activity

发出请求,调用

startActivity

行动方法,然后

Instrumentation

执行

execStartActivity

方法来将请求分发给接收者

IActivityTaskManager

,接收者

IActivityTaskManager

再调用后续流程。当然,整个流程比较复杂,这里只做简单的介绍。可以看到

Activity

本身充当了请求者的角色,

Instrumentation

充当了命令的角色,

IActivityTaskManager

则充当了接收者的角色,三者按照既定的调用流程来完成页面的启动。

总结

命令模式一般在GUI系统中使用较频繁,比如菜单项、开关机等。命令模式对于类文件的膨胀是一个不可避免的问题。但是其优点也很明显,请求者和接收更弱的耦合性,更加灵活的控制性以及更加简洁的扩展。在日常开发过程中,涉及到相关的请求-接收模型时,可以考虑命令模式。