命令即Command,是我们在计算机中经常听到的词汇,命令能够让计算机明确自己的职责。现实生活中,命令模式的例子有很多,例如电视遥控器发送的红外码就可以理解成命令模式,遥控器向电视发送请求,电视根据具体请求进行处理。这就是典型的命令模式解耦的例子。
命令模式
命令模式(Command)的定义:将发送命令者封装成对象(类比电视遥控器),使发送请求的职责与处理请求的职责分离,即解耦,使两者通过命令进行通讯。这样,当需要管理这些对象的工作情况,就可以直接管理这些命令实例的集合,而无需修改具体的处理逻辑。命令模式能够方便对象之间的调用,提高系统的可拓展性。
命令模式的结构
命令模式主要包含以下结构:
- 抽象命令(Command):定义了命令的接口和执行命令的抽象函数
execute()。 - 具体命令(ConcreteCommand):实现了抽象命令中的所有方法,并管理一个接收者(Receiver)对象,通过调用接收者(Receiver)的功能来完成命令的具体操作。
- 接收者(Receiver):接收者负责接收并执行命令的具体操作,是业务逻辑的处理类。
- 调用者(Invoker):调用者即发送请求的类,它管理着命令对象,并通过调用命令对象来执行请求,请注意,它不能直接访问接收者。
命令模式的实际应用场景
- 与硬件的串口通讯:在于硬件进行串口通信时,常常会有大量命令需要传输,如果使用
if else语句进行判断后传输给硬件,会造成代码杂乱无章,难以拓展。使用命令模式将重要指令进行封装,能够将命令的发送和处理进行逻辑分离,有利于系统的解耦。 - 复杂文本编辑器的操作:文本编辑器通常需要各种功能,例如最基本的保存、复制、粘贴操作。如果体量很小,我们当然无需使用命令模式来增加系统的复杂度。但通常实际项目的体量往往比我们设想的大得多,所以我们需要提前考虑项目的拓展。例如文本编辑器还有撤销、重做等功能。这个时候使用命令模式就是必要的。
注:在使用命令模式时,我们还可以将一系列命令以
java.util.List即列表的形式进行保存,这样我们还可以轻松维护工作的历史记录,只需要维护List实例即可。在需要执行历史命令时,只需要从实例列表中取出。
示例
在本文示例中,我们将模拟使用命令模式实现一个股票交易买卖Demo。股票的操作有许多种,Demo实现股票的买和卖两种操作,使用命令模式完成,方便后续其他操作模式的拓展。
首先,我们创建一个抽象命令接口。
Order.java
package com.yeliheng.command;
/**
* 抽象命令
*/
public interface Order {
void execute();
}
在抽象命令中,具有一个execute()方法,待会的具体命令将实现这个execute()方法。
接着,我们需要定义一个命令的接收者,即负责处理命令的具体逻辑操作。
StockCommandReceiver.java
package com.yeliheng.command;
/**
* 命令接收者,处理具体的买卖逻辑
*/
public class StockCommandReceiver {
private final String stockCode = "123456"; //股票代码
private final int quantity = 100; //买入数量
public void buy() {
System.out.println("[买入] 股票代码: " + stockCode + " 数量(股): " + quantity);
}
public void sell() {
System.out.println("[卖出] 股票代码: " + stockCode + " 数量(股): " + quantity);
}
}
在这个类中,我们定义了两种操作的方法,分别是buy()和sell()。在两个方法中分别输出股票代码和股票数量。
然后我们就可以来实现具体命令了。
BuyCommand.java
package com.yeliheng.command;
/**
* 具体命令
*/
public class BuyCommand implements Order{
private StockCommandReceiver receiver;
public BuyCommand(StockCommandReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.buy();
}
}
BuyCommand类实现了Order订单的execute()方法,并且管理了一个命令接收者实例,方便调用者Invoker进行调用。
SellCommand与BuyCommand同理,具体代码如下:
SellCommand.java
package com.yeliheng.command;
/**
* 具体命令
*/
public class SellCommand implements Order{
private StockCommandReceiver receiver;
public SellCommand(StockCommandReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.sell();
}
}
最后,我们要实现一个命令的调用者即Invoker类,该类中含有下单和处理订单的具体逻辑。并管理了一个命令列表,方便记录历史。
Invoker.java
package com.yeliheng.command;
import java.util.ArrayList;
import java.util.List;
/**
* 调用者
*/
public class Invoker {
private List<Order> orderList = new ArrayList<>();
//下单
public void takeOrder(Order order) {
orderList.add(order);
}
//处理订单
public void handleOrders() {
for(Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
在Invoker类中,我们实现了两个方法,分别是takeOrder()和handleOrders()。takeOrder()方法会将所有订单加入到订单列表中。而handleOrders()方法将遍历所有订单,负责请求具体命令,达到订单处理的效果。
完成了命令模式的基本结构后,我们在main函数中进行使用:
Main.java
package com.yeliheng.command;
public class Main {
public static void main(String[] args) {
//注册接收者
StockCommandReceiver receiver = new StockCommandReceiver();
//注册命令
BuyCommand buyCommand = new BuyCommand(receiver);
SellCommand sellCommand = new SellCommand(receiver);
//下单
Invoker invoker = new Invoker();
invoker.takeOrder(buyCommand);
invoker.takeOrder(sellCommand);
//处理订单
invoker.handleOrders();
}
}
在Main类中,我们首先实例化了一个接受者,再实例化具体命令,将具体命令交付给调用者实例进行管理,并执行具体业务逻辑。
最终程序的输出结果如下图所示:
这样就完成了一个完整命令模式的示例。
命令模式的优缺点
优点
- 命令模式通过抽象接口来降低系统的耦合度。在命令的发送与具体业务的执行上进行了分离,有利于系统的解耦和后期的拓展。当有具体命令产生时,只需要增加新的具体命令类即可,无需修改命令处理的具体代码,符合开闭原则。
- 可与多种设计模式结合使用,实现强大的事件处理系统。
注:命令也可认为是一种特殊的事件(event),合理使用命令模式能够很好地实践“事件驱动编程”的思想。
缺点
- 过度使用命令模式可能会造成具体命令类大幅增长,从而提升系统的复杂度与维护难度。
总结
本文通过股票交易系统示例进行命令模式的讲解。命令模式在Web开发中可能用得并不多,但作为一个基础的设计模式,我们也必须熟练掌握。它在底层框架上和客户端上的应用较为广泛,掌握命令模式有助于深入理解开源框架。
本文示例的完整源代码参见:Github