如何在Java中使用命令模式(详细指南)

233 阅读7分钟

学习如何使用命令模式将请求封装为对象,在你的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,而是使用一个匿名的内类,也就是实现Runnablerun() 方法的无名类。但是这种方法会使代码更加冗长。使用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显示了按钮被点击后这段代码的结果:

Command pattern with ActionListener. IDG

图1.动作中的ActionListener

驾驶我的摩托车车辆接口中的命令模式

现在你已经看到了JDK中命令模式的例子,让我们来创建我们自己的命令模式。首先,看一下图2中的类图。

Command pattern for a Vehicle interface. IDG

图2.车辆接口的命令模式图

该图有三个部分,我将对其进行解释。

命令类

命令模式的基础类是Command 接口。我们在任何时候都要使用这个接口来执行或恢复一个命令:

public interface Command {
  
  void execute();

  void revert();

}

接收者

接下来,我们需要创建一个具有执行命令行为的类。我们从Vehicle 接口开始,然后创建MotorcycleTruck 类来实现它。

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原则的一个很好的例子。(记住,这个原则指出,对象或实体应该是可扩展的)。

调用者

现在,我们有了MotorcycleTruck 行为,但我们需要一个类来执行它。在我们的例子中,这个类将是GhostRiderGhostRider 将驱动MotorcycleTruck 类。

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 。然后,它从Vehicleexecute() 方法中调用与每个命令类相对应的方法:

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…

何时使用命令模式

设计模式的一个重要规则是要知道何时使用它们。不管一个模式有多好,在错误的使用情况下实现它,会使你的代码变得更糟。这里有一些关于使用命令模式的指南:

  1. 你有多个命令,应该根据SOLID的单一责任和开放封闭的设计原则分别实现。
  2. 你需要创建可逆的命令,例如从购物车中添加和删除一个项目。
  3. 你需要在执行命令的时候能够创建日志。命令模式中的每个命令都被封装了,所以创建日志很容易。
  4. 你需要能够同时执行多个命令。你可以很容易地在一个命令的实现中添加Queue,List, 或Set ,并执行它们。

关于命令模式要记住什么

总结一下,请记住关于命令模式的以下几点:

  • 它应用了SOLID的单一责任和开放封闭的设计原则。
  • 它对命令的行为进行了封装和解耦,这使得你的代码更具扩展性。
  • 在JDK中,它被用于Thread 类和RunnableActionListener 接口。
  • 它将命令的行为封装在一个单一的Command 实现中。
  • 它可以让你执行和恢复单个命令。
  • 它可以让你同时执行和恢复多个命令。