如何在 Java 中使用命令模式
作为软件开发人员,我们面临的最大挑战之一是组织我们的代码,使其更易于扩展和维护。 命令模式通过将执行操作所需的所有数据封装到单个 Command
目的。
您可能会认出命令模式,因为我们在日常生活中一直都在使用它。 一个很好的例子是使用遥控设备打开电视、切换频道、调高音量等等。 这些动作中的每一个都被封装在远程控制设备中。
关于所有这些操作,还有一点需要注意,它们是可逆的:您可以打开电视,也可以将其关闭。 此外,某些操作必须按顺序执行:您必须先打开电视,然后才能调高音量。
在此 Java 代码挑战中,您将了解命令设计模式,并在实践中看到该模式的几个示例。 我还将讨论命令模式如何实现 SOLID 模型的两个核心原则。 这两个原则是单一职责原则,它指出一个类应该只有一个工作,以及开放封闭原则,它指出对象或实体应该对扩展开放但对修改关闭。
什么是命令模式?
命令模式是随四人组设计模式引入的 23 种设计模式之一。 命令是一种行为设计模式,这意味着它旨在以特定代码模式执行操作。
设计模式的四种类型
有关四种设计模式的概述,请参阅设计模式简介。
首次引入时,Command 模式有时被解释为 Java 的回调。 虽然它最初是一种面向对象的设计模式,但 Java 8 引入了 lambda 表达式,允许命令模式的对象功能实现。 本文包含一个在命令模式中使用 lambda 表达式的示例。
与所有设计模式一样,了解何时应用命令模式以及何时使用另一种模式可能更好是非常重要的。 对用例使用错误的设计模式会使您的代码更复杂,而不是更少。
JDK中的命令模式
我们可以在 Java 开发工具包和 Java 生态系统中找到许多命令模式的示例。 一个流行的例子是使用 Runnable
功能接口与 Thread
班级。 另一个是处理事件 ActionListener
. 让我们探讨这两个例子。
获取示例代码
获取本文中演示的命令模式示例的代码。
带 Thread 和 Runnable 的命令模式
Runnable
是一个接口,包括 run()
方法。 以下代码片段显示了 run()
方法的签名。 如您所见,可以在 run()
方法:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Thread
是最常用的类 Runnable
. 让我们看看如何将命令传递给 Thread
班级:
Runnable command = () -> System.out.println("Executing command!");
Thread thread = new Thread(command); // Setting command
thread.start();
在这段代码中,我们在 run()
带有 lambda 表达式的方法。 我们可以使用匿名内部类代替 lambda,它是一个未命名的类,它实现了 Runnable
和 run()
方法。 但这种方法会使代码更加冗长。 使用 lambda 更简洁,更易于阅读。
然后我们将命令传递给 Thread
班级。 最后,我们通过调用 start()
方法。
这是我们可以从这段代码中得到的输出:
Executing command!
带有 ActionListener 的命令模式
JDK 中的另一个很好的例子是 ActionListener
界面。 我知道这是一个较旧的界面,但它适合作为示例。
在下面的代码中,我们创建了一个 JFrame
和一个 JButton
. 然后我们通过调用 addActionListener()
方法。 在这种情况下,我们只需将文本从“单击我”更改为“已单击”。 然后,我们将按钮添加到框架,并显示带有按钮的框架:
JFrame frame = new JFrame();
JButton button = new JButton("Click Me");
button.addActionListener(e -> button.setText("Clicked!")); // Command implementation
frame.add(button);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
图 1 显示了单击按钮后此代码的结果。
IDG
图 1. 运行中的 ActionListener。
开我的摩托车! 车辆界面中的命令模式
现在您已经在 JDK 中看到了命令模式的示例,让我们来创建我们自己的模式。 首先,看一下图 2 中的类图。
IDG
图 2. 车辆接口的命令模式图。
该图分为三个部分,我将对其进行解释。
命令
命令模式的基础类是 Command
界面。 我们可以在任何时候想要执行或恢复命令时使用此接口:
public interface Command {
void execute();
void revert();
}
接收者
接下来,我们需要创建具有执行命令行为的类。 我们从 Vehicle
接口,然后创建 Motorcycle
和 Truck
实现它的类:
public interface Vehicle {
void start();
void stop();
void accelerate();
}
public class Motorcycle implements Vehicle {
@Override
public void start() {
System.out.println("Starting motorcycle...");
}
@Override
public void stop() {
System.out.println("Stopping motorcycle...");
}
@Override
public void accelerate() {
System.out.println("Accelerating motorcycle...");
}
}
public class Truck implements Vehicle {
@Override
public void start() {
System.out.println("Starting truck...");
}
@Override
public void stop() {
System.out.println("Stopping truck...");
}
@Override
public void accelerate() {
System.out.println("Accelerating truck...");
}
@Override
public void decelerate() {
System.out.println("Decelerating truck...");
}
}
另请注意 Vehicle
接口使代码更灵活,更容易更改:我们可以轻松添加另一种车辆,例如 Car
实现了 Vehicle
界面。 命令模式的这一部分是开闭 SOLID 原则的一个很好的例子。 (请记住,此原则指出对象或实体应该是可扩展的。)
调用者
现在,我们有 Motorcycle
和 Truck
行为,但我们需要一个类来执行它。 在我们的例子中,这个类将是 GhostRider
. GhostRider
将驱动 Motorcycle
和 Truck
类。
GhostRider
在构造函数中接收命令并调用 execute()
方法从命令进入 takeAction()
和 revertAction()
方法:
public class GhostRider {
Command command;
public GhostRider(Command command){
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void takeAction(){
command.execute();
}
public void revertAction() {
command.revert();
}
}
在命令模式中实现命令
现在,让我们创建 StartMotorcycle
, AccelerateMotorcycle
, 和 StartAllVehicles
命令。 每个命令实现 Command
接口和接收 Vehicle
在构造函数中。 然后,它调用对应于每个命令类的方法 Vehicle
进入 execute()
方法:
public class StartMotorcycle implements Command {
Vehicle vehicle;
public StartMotorcycle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public void execute() {
vehicle.start();
}
@Override
public void revert() {
vehicle.stop();
}
}
public class AccelerateMotorcycle implements Command {
Vehicle vehicle;
public AccelerateMotorcycle(Vehicle vehicle){
this.vehicle = vehicle;
}
public void execute() {
vehicle.accelerate();
}
@Override
public void revert() {
vehicle.decelerate();
}
}
import java.util.List;
public class StartAllVehicles implements Command {
List<Vehicle> vehicles;
public StartAllVehicles(List<Vehicle> vehicles) {
this.vehicles = vehicles;
}
public void execute() {
vehicles.forEach(vehicle -> vehicle.start());
}
@Override
public void revert() {
vehicles.forEach(vehicle -> vehicle.stop());
}
}
运行命令
是时候运行我们的命令了! 为此,我们首先实例化 Motorcycle
类有 Command
行为,然后将其传递给每个 Command
执行。
请注意,我们还使用了 StartAllVehicles
命令一次启动(和停止)多辆车。
然后,我们实例化 GhostRider
将执行每个命令的类。 最后,我们调用 takeAction()
和 revertAction()
方法:
public class RideVehicle {
public static void main(String[] args) {
Vehicle motorcycle = new Motorcycle();
StartMotorcycle startCommand = new StartMotorcycle(motorcycle);
GhostRider ghostRider = new GhostRider(startCommand);
ghostRider.takeAction();
AccelerateMotorcycle accelerateCommand = new AccelerateMotorcycle(motorcycle);
ghostRider.setCommand(accelerateCommand);
ghostRider.takeAction();
ghostRider.revertAction();
Vehicle truck = new Truck();
List<Vehicle> vehicles = List.of(motorcycle, truck);
StartAllVehicles startAllVehicles = new StartAllVehicles(vehicles);
startAllVehicles.execute();
startAllVehicles.revert();
}
}
这是此代码的输出:
Starting motorcycle...
Accelerating motorcycle...
Decelerating motorcycle...
Starting motorcycle...
Starting truck...
Stopping motorcycle...
Stopping truck…
何时使用命令模式
设计模式的一个重要规则是知道何时使用它们。 无论模式多么出色,为错误的用例实施它都会使您的代码变得更糟。 以下是使用命令模式的一些指南:
- 您有多个命令应该根据单一职责和开闭设计的 SOLID 原则分别实施。 您需要创建可逆命令,例如在购物车中添加和删除商品。 每当执行命令时,您都需要能够创建日志。 命令模式中的每个命令都被封装,因此创建日志很容易。 您需要能够一次执行多个命令。 您可以轻松地添加一个
Queue
, List
, 或者 Set
进入命令的实现并执行它们。
命令模式的企业用例
这里开发的用例实现了命令模式,但它不是真实世界的情况。 有关企业应用程序中更常见的用例,请参阅我关于使用命令模式在企业购物车中应用折扣的讨论。
有关命令模式的注意事项
总而言之,请记住以下有关命令模式的内容:
- 它应用单一职责和开闭设计的 SOLID 原则。 它封装和解耦了命令的行为,这使您的代码更具可扩展性。 它在 JDK 中与
Thread
类和 Runnable
和 ActionListener
接口。 它将命令的行为封装在一个单一的 Command
执行。 它允许您执行和恢复单个命令。 它允许您一起执行和恢复多个命令。