学习如何使用命令模式将请求封装为对象,在你的Java程序中单独或依次执行。
作为软件开发者,我们最大的挑战之一是组织我们的代码,使其更容易扩展和维护。命令模式通过将执行一个动作所需的所有数据封装到一个Command 对象中来帮助我们做到这一点。
你可能认识到命令模式,因为我们在日常生活中一直在使用它。一个很好的例子是使用遥控设备来打开电视,切换频道,调高音量,等等。这些动作中的每一个都被封装在遥控器设备中。
关于所有这些动作,还有一点需要注意的是,它们是可逆的:你可以打开电视,也可以把它关掉。此外,有些动作必须按顺序进行:你必须先打开电视,然后才能调高音量。
在这个Java代码挑战中,你将了解到Command设计模式,并看到该模式在实践中的几个例子。我还将讨论Command模式如何实现SOLID模型的两个核心原则。这两条原则是:单一责任原则,即一个类应该只有一个工作;以及开放-封闭原则,即对象或实体应该是开放的,可以进行扩展,但对修改是封闭的。
什么是命令模式?
命令模式是 "四人帮 "设计模式中引入的23种设计模式之一。命令是一种行为设计模式,意味着它的目的是在一个特定的代码模式中执行一个动作。
当它第一次被引入时,Command模式有时被解释为Java的回调。虽然它一开始是一种面向对象的设计模式,但Java 8引入了lambda表达式,允许对Command模式进行对象功能的实现。本文包括一个在Command模式中使用lambda表达式的例子。
就像所有的设计模式一样,知道什么时候应用Command模式,以及什么时候使用另一种模式会更好,是非常重要的。在一个用例中使用错误的设计模式会使你的代码更加复杂,而不是更少。
JDK中的 "命令 "模式
在Java开发包和Java生态系统中,我们可以找到许多命令模式的例子。一个流行的例子是使用Runnable 功能接口和Thread 类。另一个是用ActionListener 来处理事件。让我们来探讨一下这两个例子。
带有 Thread 和 Runnable 的 Command 模式
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();
在这段代码中,我们用一个lambda表达式来实现run() 方法中的命令行为。我们可以不使用lambda,而是使用一个匿名的内类,也就是实现Runnable 和run() 方法的无名类。但是这种方法会使代码更加冗长。使用lambda更简明,更容易阅读。
然后我们将命令传递给Thread 类。最后,我们通过调用start() 方法来执行该命令。
下面是我们可以从这段代码中期待的输出:
Executing command!
带有ActionListener的命令模式
JDK中的另一个好例子是ActionListener 接口。我知道这是一个老的接口,但它适合作为一个例子。
在下面的代码中,我们创建了一个JFrame 和一个JButton 。然后我们通过调用addActionListener() 方法来设置按钮中的动作。在这种情况下,我们只是将文本从 "Click me "改为 "Clicked"。然后,我们将该按钮添加到框架中,并显示带有该按钮的框架:
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());
}
}
运行命令
现在是运行我们的命令的时候了!为此,我们首先实例化了具有Command 行为的Motorcycle 类,然后将其传递给每个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实现中。 - 它可以让你执行和恢复单个命令。
- 它可以让你同时执行和恢复多个命令。