为什么我可以使用命令设计模式,而我可以轻松调用所需的方法?

Dan*_*own 45 java oop design-patterns command-pattern

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

我为什么不应该使用合闸合闸()/关机灯类,而不是单独的类和方法,最终调用合闸合闸/关机方法()方法?

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

如果你知道,你解决了可以使用这种设计模式请与我共享来解决任何复杂的现实世界的问题.它帮助我和这篇文章的未来读者更好地理解这种设计模式的用法.谢谢

//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();
  }
}
Run Code Online (Sandbox Code Playgroud)

为什么我不应该轻易使用如下代码?

 Light light = new Light();
 switch(light.command) {
  case 1:
    light.switchOn();
    break;
  case 2:
    light.switchOff();
    break;
 }
Run Code Online (Sandbox Code Playgroud)

spr*_*ter 41

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

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

在你的榜样,你可以有其他类(如Air Conditioner拥有自己的命令(例如)Turn Thermostat Up,Turn 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);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以做以下事情:

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());
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,Button并且Scheduler根本不需要了解任何有关命令的信息.Scheduler是一个可能包含命令集合的类的示例.

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

@FunctionalInterface
interface Command {
    void execute();
}

public Light {
    public void turnOn();
}

new Button("Turn On Light", light::turnOn);   
Run Code Online (Sandbox Code Playgroud)

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

  • @DanielNewtown如果您将命令分配给按钮(例如),按钮必须知道该命令是用于灯还是空调.命令模式确保它不需要知道有关执行它的命令的任何信息. (5认同)

hol*_*roy 23

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

  • 隐藏命令执行方式的实际实现
  • 允许围绕命令构建方法,也就是命令扩展

隐藏实施

在大多数编程中,您都希望隐藏实现,以便在查看最顶层的问题时,它包含一个易于理解的命令/代码子集.即你不需要/想要了解如何打开灯或汽车启动的血腥细节.如果您的重点是让汽车启动,您不需要了解发动机的工作原理,以及燃料如何进入发动机,阀门如何工作,......

指示行动,而不是如何做

命令为您提供此类视图.您将立即了解该TurnLightOn命令的作用,或StartCar.使用命令,您将隐藏有关某些事情的详细信息,同时清楚地指出要执行的操作.

允许更改内部细节

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

可能的命令扩展

使用Command接口可以在使用命令的代码和执行命令实际操作的代码之间提供额外的层.这可以允许多个好的场景.

安全扩展或接口暴露

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

但是,从外部看,您可能希望限制对灯光的访问,以便仅打开或关闭灯光.使用命令可以将界面限制为类.

此外,如果需要,您可以围绕命令构建专用的用户访问系统.这将使您的所有业务逻辑保持开放和可访问且不受限制,但您仍然可以在命令级别轻松限制访问以强制执行适当的访问.

执行东西的通用接口

在构建足够大的系统时,命令提供了一种巧妙的方法来桥接不同的模块/库.您可以查看访问该类的命令,而不是需要检查任何给定类的每个实现细节.

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

命令的排序

使用命令,您还可以执行命令排序等操作.那是因为如果你正在执行TurnOnLightStartCar命令你并不重要,你可以执行相同的序列,因为它们以相同的方式执行.这反过来可以允许执行命令链,这在多种情况下可能是有用的.

您可以构建宏,执行您认为组合在一起的命令集.即命令序列:UnlockDoor,EnterHouse,TurnOnLight.一个自然的命令序列,但不太可能在使用不同的对象和动作时成为一个方法.

命令序列化

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

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

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

跟踪命令

如果这是业务需要,在系统中使用额外的命令层也可以允许记录或跟踪命令.您可以在命令模块中收集跟踪/记录,而不是在整个地方执行此操作.

这样可以轻松更改日志记录系统,或添加诸如计时或启用/禁用日志记录等内容.它在命令模块中很容易维护.

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


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


ysh*_*vit 12

可能性很多,但它通常是这样的:

  • 构建一个命令行框架,用于从操作中抽象出选项的解析.然后你可以用类似的东西注册一个动作opts.register("--on", new LightOnCommand()).
  • 让用户拖放一系列操作以作为宏执行
  • 在某些事件被触发时注册回调,例如 on(Event.ENTER_ROOM, new LightOnCommand())

这里的一般模式是你有一段代码负责确定在不知道该动作是什么的情况下需要采取某些动作,而另一部分代码知道如何做一个动作但不知何时做什么它.

例如,在第一个示例中,opts实例知道当它看到命令行选项--on时,它应该打开灯.但它知道这一点,却并不知道"打开灯"意味着什么.实际上,很可能opts实例来自第三方库,所以它无法了解灯光.所有它知道的是如何将动作(命令)与它解析的命令行选项相关联.

  • 对.您基本上有三段代码:一段定义命令和事件; 另一个人知道Lights等.这两件事彼此不了解,可能在不同的项目中.然后第三个部分,即"主要"组件,知道这两个部分并将它们拼接在一起. (2认同)
  • 此模式有用的另一种情况是实现多级撤消.如果`Command`接口有一个`undo()`方法和`execute()`方法,具体类实现`undo()`,它们完全颠倒了`execute()的效果`,然后你可以在执行时将命令推送到堆栈,并弹出和`undo()`来撤消任意次数. (2认同)

jhn*_*jhn 7

你不必.设计模式只是过去一些人在编写具有重大复杂性的应用程序时发现有用的指南.

在你的情况下,如果你要做的是打开和关闭灯开关,而不是其他,第二个选项是没有脑子.

更少的代码几乎总是比更多的代码更好.


Ian*_*ose 5

ICommand您提供的示例相当有限,仅在没有lambda表达式的编程语言中实际使用.Sprinter在他的答案中详细介绍了使用命令工厂.

例如,命令模式的大多数情况包括其他方法CanRun和/或Undo.这些允许按钮根据命令的状态或实现撤消堆栈的应用程序更新其启用状态.

与大多数设计模式一样,命令模式也会变得更加复杂.它也是众所周知的,因此有助于为大多数程序员清楚地编写代码.