为什么我应该使用命令设计模式,而我可以很容易地调用所需的方法?[已关闭]

2022-09-01 00:26:22

我正在研究命令设计模式,我对使用它的方式感到非常困惑。我拥有的示例与用于打开和关闭灯的远程控制类相关。

为什么我不应该使用Light类的switchOn()/switchOff()方法,而不是使用最终调用switchOn/switchOff方法的单独类和方法?

我知道我的例子很简单,但这就是重点。我无法在互联网上的任何地方找到任何复杂的问题来查看命令设计模式的确切用法。

如果您知道您可以使用此设计模式解决的任何复杂的现实世界问题,请与我分享。它帮助我和本文的未来读者更好地了解此设计模式的用法。谢谢

//Command
public interface Command {
  public void execute();
}

//Concrete Command
public class LightOnCommand implements Command {

  //Reference to the light
  Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.switchOn();        //Explicit call of selected class's method
  }
}

//Concrete Command
public class LightOffCommand implements Command {

  //Reference to the light
  Light light;

  public LightOffCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.switchOff();
  }
}

//Receiver
public class Light {
  private boolean on;

  public void switchOn() {
    on = true;
  }

  public void switchOff() {
    on = false;
  }
}

//Invoker
public class RemoteControl {
  private Command command;

  public void setCommand(Command command) {
    this.command = command;
  }

  public void pressButton() {
    command.execute();
  }
}

//Client
public class Client {
  public static void main(String[] args) {
    RemoteControl control = new RemoteControl();
    Light light = new Light();
    Command lightsOn = new LightsOnCommand(light);
    Command lightsOff = new LightsOffCommand(light);

    //Switch on
    control.setCommand(lightsOn);
    control.pressButton();

    //Switch off
    control.setCommand(lightsOff);
    control.pressButton();
  }
}

为什么我不能轻易地使用如下代码?

 Light light = new Light();
 switch(light.command) {
  case 1:
    light.switchOn();
    break;
  case 2:
    light.switchOff();
    break;
 }

答案 1

使用 Command 模式的主要动机是,命令的执行者根本不需要知道命令是什么、它需要什么上下文信息或它做什么。所有这些都封装在命令中。

这允许您执行某些操作,例如具有按顺序执行的命令列表,这些命令依赖于其他项目,分配给某些触发事件等。

在您的示例中,您可以有其他类(例如 ),它们有自己的命令(例如 , )。这些命令中的任何一个都可以分配给按钮,或者在满足某些条件时触发,而无需任何命令知识。Air ConditionerTurn Thermostat UpTurn Thermostat Down

因此,总而言之,该模式封装了执行操作所需的所有内容,并允许操作的执行完全独立于该上下文的任何上下文发生。如果这不是您的要求,那么该模式可能对您的问题空间没有帮助。

下面是一个简单的用例:

interface Command {
    void execute();
}

class Light {
    public Command turnOn();
    public Command turnOff();
}

class AirConditioner {
    public Command setThermostat(Temperature temperature);
}

class Button {
    public Button(String text, Command onPush);
}

class Scheduler {
    public void addScheduledCommand(Time timeToExecute, Command command);
}

然后,您可以执行如下操作:

new Button("Turn on light", light.turnOn());
scheduler.addScheduledCommand(new Time("15:12:07"), airCon.setThermostat(27));
scheduler.addScheduledCommand(new Time("15:13:02"), light.turnOff());

如您所见,并且根本不需要了解有关命令的任何信息。 是可能包含命令集合的类的示例。ButtonSchedulerScheduler

另请注意,在 Java 8 中,函数接口和方法引用使这种类型的代码更加整洁:

@FunctionalInterface
interface Command {
    void execute();
}

public Light {
    public void turnOn();
}

new Button("Turn On Light", light::turnOn);   

现在,转换为命令的方法甚至不需要知道命令 - 只要它们具有正确的签名,您就可以通过引用该方法悄悄地创建匿名命令对象。


答案 2

让我们重点介绍命令设计的非实现方面,以及使用命令设计模式的一些主要原因,分为两大类:

  • 隐藏命令执行方式的实际实现
  • 允许围绕命令(又称命令扩展)构建方法

隐藏实现

在大多数编程中,您需要隐藏实现,以便在查看最上面的问题时,它由可理解的命令/代码子集组成。也就是说,您不需要/想知道如何打开灯或启动汽车的血腥细节。如果你的重点是让汽车启动,你不需要了解发动机是如何工作的,以及它如何需要燃料进入发动机,气门是如何工作的,......

指示操作,而不是操作的完成方式

命令为您提供这种视图。您将立即了解该命令的作用,或 .使用命令,您将隐藏如何完成某些操作的详细信息,同时清楚地指示要执行的操作。TurnLightOnStartCar

允许更改内部细节

此外,假设您稍后重新构建整个类或一个类,这需要您实例化几个不同的对象,并且可能需要在实际执行所需的操作之前执行其他操作。在这种情况下,如果您已经实现了直接访问方法,则在很多地方,您需要在事先编码的所有地方更改它。使用命令,您可以更改如何执行操作的内部详细信息,而无需更改命令的调用。LightCar

可能的命令扩展

使用 Command 接口会在使用命令的代码和执行命令实际操作的代码之间增加一层。这可以允许多个良好的方案。

安全扩展或接口公开

使用命令界面,您还可以限制对对象的访问,从而允许您定义另一个安全级别。拥有一个具有相当开放访问权限的模块/库可能是有意义的,这样您就可以轻松处理内部特殊情况。

但是,从外部看,您可能希望限制对光源的访问,以便仅将其打开或关闭。使用命令使您能够将接口限制为类

此外,如果需要,可以围绕命令构建专用的用户访问系统。这将使所有业务逻辑保持开放和可访问状态,并且没有限制,但您仍然可以轻松地在命令级别限制访问以强制实施正确的访问。

执行东西的通用接口

当构建一个足够大的系统时,命令提供了一种在不同模块/库之间架桥的简洁方法。您不需要检查任何给定类的每个实现细节,而是可以查看哪些命令正在访问该类。

由于您省略了命令本身的实现细节,因此可以使用通用方法来实例化命令,执行命令并查看结果。这允许更容易编码,而不需要阅读如何实例化该特定或类,并确定其结果。LightCar

命令排序

使用命令,您还可以执行命令排序之类的操作。也就是说,由于您是否正在执行 or 命令并不重要,因此您可以执行这些序列,因为它们的执行方式相同。这反过来又可以允许执行命令链,这在多种情况下可能很有用。TurnOnLightStartCar

您可以构建宏,执行您认为组合在一起的命令集。即命令序列:、 、 .命令的自然序列,但不太可能成为方法,因为它使用不同的对象和操作。UnlockDoorEnterHouseTurnOnLight

命令序列化

命令本质上是相当小的,也允许序列化非常好。这在服务器-客户端上下文或程序-微服务上下文中很有用。

服务器(或程序)可以触发一个命令,然后序列化该命令,通过某种通信协议(即事件队列,消息队列,http,...)将其发送给实际处理该命令的人。无需首先在服务器上实例化对象,即可以是轻量级的(双关语),也可以是非常大的结构。您只需要命令,可能还需要几个参数。LightCar

这可能是引入 CQRS - 命令查询责任分离模式以进行进一步研究的好地方。

跟踪命令

在系统中使用额外的命令层还可以允许记录或跟踪命令(如果这是业务需求)。您可以在命令模块中收集跟踪/日志记录,而不是到处执行此操作。

这允许轻松更改日志记录系统,或添加诸如计时或启用/禁用日志记录之类的内容。而且,这一切都可以在命令模块中轻松维护。

命令跟踪还允许撤消操作,因为您可以选择从给定状态重新迭代命令。需要一点额外的设置,但很容易做到。


简而言之,命令可能非常有用,因为它们允许在即将成为大型程序的不同部分之间建立连接,因为它们很轻,易于记忆/记录,并在需要时隐藏实现细节。此外,这些命令允许几个有用的扩展,在构建更大的系统时会派上用场:即通用接口,排序,序列化,跟踪,日志记录,安全性。