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

我正在研究命令devise模式 ,我对使用它的方式感到困惑。 我有的例子是关于远程控制类,用来打开和closures灯。

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

我知道我的例子很简单 ,但这是重点。 我无法在Internet上的任何位置find任何复杂的问题,以查看命令devise模式的确切用法。

如果你知道你解决的任何复杂的现实世界问题,可以使用这种devise模式解决,请与我分享。 这有助于我和这篇文章的未来读者更好地理解这种devise模式的用法。 谢谢

//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; } 

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

这允许你做一些事情,比如有一系列依次执行的命令,这些命令依赖于其他的项目,分配给某些触发事件等。

在你的例子中,你可以有其他的类(如Air Conditioner )有自己的命令(例如Turn Thermostat UpTurn Thermostat Down )。 任何这些命令都可以分配给一个button,或者在满足某些条件时触发,而不需要任何有关该命令的知识。

因此,总而言之,模式封装了采取行动所需的一切,并允许行为的执行完全独立于任何上下文。 如果这不是你的要求,那么这个模式可能对你的问题空间没有帮助。

这是一个简单的用例:

 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()); 

正如你所看到的, ButtonScheduler不需要知道关于这些命令的任何信息。 Scheduler是可能包含一组命令的类的示例。

还要注意,在Java 8中,函数接口和方法引用使得这种types的代码更加整洁:

 @FunctionalInterface interface Command { void execute(); } public Light { public void turnOn(); } new Button("Turn On Light", light::turnOn); 

现在变成命令的方法甚至不需要知道命令 – 只要它们具有正确的签名,就可以通过引用方法悄悄地创build一个匿名命令对象。

让我们专注于命令devise的非实现方面,使用命令devise模式的一些主要原因分为两大类:

  • 隐藏命令执行的实际执行
  • 允许方法围绕命令构build,即命令扩展

隐藏实现

在大多数编程中,您会想隐藏实现,以便在查看最顶层的问题时,它包含一个可理解的命令/代码子集。 也就是说,你不需要/想要知道灯光开启的细节,或者汽车启动。 如果你的重点是让汽车启动,你不需要了解发动机是如何工作的,如何进入发动机需要燃料,阀门是如何工作的…

指出行动,而不是如何做

一个命令给你这种观点。 您将立即明白TurnLightOn命令或StartCar使用一个命令,你会隐藏如何做的细节,同时明确指出要执行的动作。

允许改变内部细节

另外,稍后可以说你重build你的整个Light类,或者一个Car类,这个类需要你实例化几个不同的对象,在实际执行你想要的操作之前,你可能需要别的东西。 在这种情况下,如果您已经实施了直接访问方法,那么在很多地方,您需要事先在所有编码的地方进行更改。 使用一个命令,你可以在不改变命令调用的情况下,改变如何做内容的内部细节。

可能的命令扩展

使用Command接口为您​​提供了使用该命令的代码和执行该命令的实际操作的代码之间的附加层。 这可以允许多个好的scheme。

安全扩展或接口暴露

使用命令界面,您还可以限制对象的访问权限,允许您定义另一个安全级别。 有一个相当开放的访问模块/库,可以很容易地处理内部的特殊情况。

但是,从外部来看,您可能需要限制对光线的访问,以便只能打开或closures。 使用命令使您能够将接口限制为一个类

另外,如果你愿意的话,你可以在命令周围build立一个专用的用户访问系统。 这将使您的所有业务逻辑都处于开放和可访问的状态,并且不受限制,但是您仍然可以轻松地限制在命令级别的访问,以实施适当的访问。

通用接口来执行的东西

当build立一个足够大的系统时,命令给出了在不同的模块/库之间进行桥接的简单方法。 您不需要检查任何给定类的每个实现细节,而是可以查看哪些命令正在访问该类。

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

命令的sorting

使用命令,你也可以执行诸如命令sorting之类的东西。 这是因为如果您正在执行TurnOnLightStartCar命令,则StartCar ,您可以按照相同方式执行这些命令序列。 这又可以允许命令链被执行,这在多种情况下可能是有用的。

你可以build立macros,执行你认为是分组在一起的命令集。 即命令序列: UnlockDoorEnterHouseTurnOnLight 。 一个自然的命令序列,但不可能因为使用不同的对象和动作而变成一个方法。

命令序列化

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

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

这可能是介绍CQRS – 命令查询责任分离模式进一步研究的好地方。

跟踪命令

在系统中使用额外的命令层也可以允许logging或跟踪命令,如果这是业务需要。 而不是到处都是这样,你可以在命令模块中收集跟踪/logging。

这允许轻松更改日志logging系统,或添加诸如计时,启用/禁用日志logging等function。 而且在命令模块中很容易维护。

对命令的跟踪还允许允许撤消操作,因为您可以select从给定状态重新执行命令。 需要一些额外的设置,但相当容易实现。


简而言之,命令可以是非常有用的,因为它们允许即将到来的程序的不同部分之间的连接,因为它们轻便易于记忆/logging,并在需要时隐藏实现细节。 另外,这些命令允许进行一些有用的扩展,这些扩展在构build更大的系统时将派上用场:即通用接口,sorting,序列化,跟踪,日志logging和安全性。

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

  • 构build一个命令行框架,从操作中抽象出选项的parsing。 然后你可以用opts.register("--on", new LightOnCommand())来注册一个动作。
  • 让用户拖放一系列的动作来执行macros
  • 注册一个callback,当一些事件被触发,就像on(Event.ENTER_ROOM, new LightOnCommand())

这里的一般模式是,你有一块代码负责确定需要采取一些行动,而不知道该做什么,另一块代码知道如何做一个行动,但不知道该做什么它。

例如,在第一个例子中, opts实例知道当它看到一个命令行选项–on时,它应该打开灯。 但它知道这一点,却不知道“打开灯”是什么意思。 实际上,这个select实例可能来自第三方库,所以它不能了解灯光。 它所知道的只是如何将操作(命令)与它分析的命令行选项相关联。

你不需要。 devise模式只是过去一些人在编写复杂程度很高的应用程序时发现的有用指南

在你的情况下,如果你需要做的是打开和closures电灯开关,而不是其他的,第二个选项是不容易的。

更less的代码总是比更多的代码更好。

你给的例子ICommand是相当有限的,只有在没有lambdaexpression式的编程语言中才是真正的用法。 Sprinter在他的回答中详细介绍了使用命令工厂的情况。

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

和大多数devise模式一样,Command模式也变得更加复杂。 这也是非常清楚的,所以有助于大多数程序员清楚地了解你的代码。

你可以做任何你想做的事情,但是遵循代码manageabilityusability模式是很好的。

在现实生活中, Light将会是一个界面。 你有像LEDLightTubeLight一样的Light不同实现

如果通过InovkerRemoteControl )执行具体命令,则不必担心Receiver中方法名称的更改。 switchOn() in Light can be changed to switchOnDevice() in future 。 但是它并不影响Invoker,因为ConcreteCommandLightsOnCommand )会做相关的修改。

假设情况是将接口发布到一个服务(服务A),并在其他服务(服务B)中实现。

现在服务A不应该知道Receiver变化。

Invoker提供消息的发送者和接收者之间的松散耦合。

看看更多相关的SE问题:

使用命令devise模式

命令模式似乎不必要的复杂(我不明白什么?)

我曾经体验过Command模式的好处。 但是首先,我想提醒一下,这个模式也是为了提高代码的可读性,并为您的(可能)共享代码库带来一些共同的理解。

我使用这种模式从面向方法的接口转换到面向命令的接口。 这意味着,我将方法调用与必要的数据一起封装到具体的命令中。 它使代码更具可读性是的,但更重要的是,我可以将方法作为对象来处理,使您可以轻松地添加/删除/修改命令,而不会增加代码的复杂性。 所以它易于pipe理,易于阅读。

其次,既然你有你的方法作为对象,你可以存储/排队他们稍后执行它们。 或者在执行完毕后取消它们。 这是这种模式可以帮助你实现“撤销”的地方。

最后,命令模式也被用来解耦命令执行和命令本身。 这就像一个服务员不知道如何烹饪他收到的订单。 他不在乎。 他不必知道。 如果他知道的话,他也会当厨师的。 如果餐馆老板想要侍者开火,他/她最后也不会做饭。 当然这个定义不是特别的或只与命令模式有关。 这就解释了为什么我们需要一般的解耦或依赖pipe理。