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

本文深入解析命令模式的概念,包括其定义、组成元素、实现方式及应用场景。通过实例代码展示了如何使用命令模式封装请求,实现请求的参数化、队列化、日志记录以及可撤销操作。此外,还探讨了命令模式在宏命令、队列请求和日志记录等方面的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【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被执行前的状态。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值