认真学习设计模式之命令模式(Command Pattern)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 认真学习设计模式之命令模式(Command Pattern)

【1】命令模式

① 定义

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象

命令模式也支持可撤销的操作。命令模式又称为行动(Action)模式或交易(Transaction)模式

命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同


每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。也就是说“发出请求的对象”和“接受与执行这些请求的对象”分隔开来。


一个命令对象通过在特定接收者上绑定一组动作来封装一个请求,要达到这一点,命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。


定义命令模式类图

五大对象


Command(抽象命令类):抽象出命令对象,可以根据不同的命令类型。写出不同的实现类。


ConcreteCommand(具体命令类):实现了抽象命令对象的具体实现。


Invoker(调用者/请求者):请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令来之间存在关联。在程序运行时,将调用命令对象的execute() ,间接调用接收者的相关操作。


Receiver(接收者):接收者执行与请求相关的操作,真正执行命令的对象。具体实现对请求的业务处理。未抽象前,实际执行操作内容的对象。


Client(客户端):在客户类中需要创建调用者对象,具体命令类对象,在创建具体命令对象时指定对应的接收者。发送者和接收者之间没有直接关系,都通过命令对象来调用。


空命令对象

如果不想每次都检查命令对象是否为null,则可以指定一个默认的对象-NoCommand:

public class NoCommand implements Command {
  public void execute(){};
}

NoCommand对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为是一种设计模式。


宏命令

在宏命令中,用命令数组存储一大堆的命令,当这个宏命令被执行时,就一次性执行数组里的每个命令。

public class MacroCommand implements Command {
  Command[] commands;
  public MacroCommand(Command[] commands){
    this.commands=commands;
  }
  public void execute(){
    for(int i=0;i<commands.length;i++){
      commands[i].execute();
    }
  }
}

宏命令是命令的一种简单的延伸,允许调用多个命令。

为何命令对象不直接实现execute()方法的细节?


也就是接收者一定有必要存在吗?一般来说我们尽量设计“傻瓜”命令对象,它只懂得调用一个接收者的一个行为。然而有许多“聪明”命令对象会实现许多逻辑,直接完成一个请求。当然可以设计聪明的命令对象,只是这样一来,调用者和接收者之间的解耦程度是比不上“傻瓜”命令对象的,而且,你也不能把够把接收者当做参数传给命令。


【2】代码实现实例

① 首先定义一个命令的接收者,也就是到最后真正执行命令的那个人。

public class Receiver {
   public void action() {
       System.out.println("命令模式的命令被执行了......");
   }
}

② 然后定义抽象命令和抽象命令的具体实现,具体命令类中需要持有真正执行命令的那个对象。

public interface Command {
   // 调用命令
   public void execute();
}
//命令对象:接收者和动作
public class ConcreteCommand implements Command{
   private Receiver receiver; //持有真正执行命令对象引用
   public ConcreteCommand(Receiver receiver) {
       super();
       this.receiver = receiver;
   }
   @Override
   public void execute() {
       //调用接收者执行命令的方法
       receiver.action();
   }
}

③ 接下来就可以定义命令的发起者了,发起者需要持有一个命令对象。以便来发起命令。

public class Invoker {
   private Command command; //持有命令对象的引用
   public Invoker(Command command) {
       super();
       this.command = command;
   }
   public void call() {
       // 请求者调用命令对象执行命令的那个execute方法
       command.execute();
   }
}

调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。调用者可以接受命令当做参数,甚至在运行时动态地进行–多态,动态绑定。


④ 客户端

public class Client {
   public static void main(String[] args) {
       //通过请求者(Invoker)调用命令对象(Command),命令对象中调用命令具体执行者(Receiver)
       Command command = new ConcreteCommand(new Receiver());
       Invoker invoker = new Invoker(command);
       invoker.call();
   }
}

代码的UML图如下:

使用场景

  • Struts2中action中的调用过程中存在命令模式。
  • 数据库中的事务机制的底层实现。
  • 命令的撤销和恢复:增加相应的撤销和恢复命令的方法(比如数据库中的事务回滚)。


命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:


(1)命令模式使新的命令很容易地被加入到系统里。

(2)允许接收请求的一方决定是否要否决请求。

(3)能较容易地设计一个命令队列。

(4)可以容易地实现对请求的撤销和恢复。

(5)在需要的情况下,可以较容易地将命令记入日志。


【3】命令模式更多用途

① 队列请求


命令模式可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:日程安排(Scheduler)、线程池、工作队列等。


想象有一个工作队列:你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。。。

请注意,工作队列类和进行计算的对象之间是完全解耦的。工作队列不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute()方法。类似地,它们只要是实现命令模式的对象,就可以放入队列,当线程可用时,就调用次对象的execute()方法。


② 日志请求


某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后重新调用这些动作恢复到之前的状态。通过新增两个方法(store()/load()),命令模式就能够支持这一点。在Java中,我们可以利用对象的序列化(Serialization)实现这些方法,但是一般认为序列化最好还是只用在对象的持久化上(persistence)。


我们可以这样实现:当我们执行命令的时候,将历史记录储存在磁盘中。一旦系统死机,我们就可以将命令对象重新加载,并成批地依次调用这些对象的execute()方法。

【4】总结


命令模式将发出请求的对象和执行请求的对象解耦。在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。


调用者通过调用命令对象的execute方法发出请求,这会使得接收者的动作被调用。


调用者可以接收命令当做参数,甚至在运行时动态地进行。


命令可以支持撤销,具体做法是实现一个undo方法来回到execute被执行前的状态。


宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销。


实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
设计模式 存储 算法
Java设计模式-命令模式(16)
Java设计模式-命令模式(16)
|
2月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
2月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
3月前
|
设计模式 存储 Java
【十二】设计模式~~~行为型模式~~~命令模式(Java)
文章详细介绍了命令模式(Command Pattern),这是一种对象行为型模式,用于将请求封装成对象,实现请求发送者与接收者的解耦,从而降低系统耦合度、提高灵活性,并支持命令的排队、记录、撤销和恢复操作。通过案例分析、结构图、时序图和代码示例,文章展示了命令模式的组成部分、实现方式和应用场景,并讨论了其优点、缺点和适用情况。
|
4月前
|
设计模式 JavaScript API
js设计模式【详解】—— 命令模式
js设计模式【详解】—— 命令模式
44 6
|
5月前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
45 2
|
5月前
|
设计模式 uml
设计模式学习心得之前置知识 UML图看法与六大原则(下)
设计模式学习心得之前置知识 UML图看法与六大原则(下)
40 2
|
5月前
|
设计模式
命令模式-大话设计模式
命令模式-大话设计模式
|
5月前
|
设计模式
设计模式-05建造者模式(Builder Pattern)
设计模式-05建造者模式(Builder Pattern)
|
5月前
|
设计模式 Java uml
必知的技术知识:JAVA【设计模式】命令模式
必知的技术知识:JAVA【设计模式】命令模式
29 0