41

有一次我讨论了与命令模式相关的设计。我的同行表示,在调用 .execute() 方法后,命令对象不应返回状态(成功、不成功以及原因)。原因是您不应该担心命令是否被执行,因为命令必须不包含任何状态。但是,您必须在调用后检查该命令是否具有预期效果。他认为的另一点是,在四人帮中,命令模式不存在这种情况(返回状态)。

我主张相反的观点。GoF 不提供这种情况,但可以根据您的需要对模式进行建模。如果命令不成功,调用客户端必须收到状态证明,并最终部署适当的反应。通过强制客户端检查操作是否成功很容易出错并产生重复的代码。此外,在某些情况下,命令会产生结果(例如,在绘图中添加线的命令,会以某种方式将线 id 返回给客户端),并且假装有没有状态的命令意味着您必须从数据模型中“捞出”新的对象标识符。

最后,我们达成了妥协,不返回状态,而是将新创建的对象的 id 保留在命令对象中,无论如何应用程序运行良好,但我现在也很想知道你的意见。

4

9 回答 9

28

我现在没有设计模式:可重用的面向对象软件的元素,但我很确定作者甚至说他们提出的设计模式是一个可以修改以适应特定的模型情况。

这个问题切入了设计模式的核心——模板。这不是必须按规定实施的东西。您确定了一个案例,其中对书中介绍的模式进行逻辑修改会帮助应用程序,这非常好,尤其是在您权衡收益和成本之后。

于 2009-07-20T18:00:52.467 回答
13

问题中有两个问题,有多个答案:) 第一个问题是命令是否应该返回错误状态?

每次应用该模式时,每个程序都没有明确的答案,您必须重新考虑它。

您需要考虑的一件事是:

  • 我是否仅针对某些特定错误情况为许多命令和客户端添加了更多耦合?

在最坏的情况下,您有许多不关心错误的命令,但是一两个命令会做一些重要的事情,让客户端知道它是否有效。您现在将检查的异常添加到接口,因此每个客户端和每个命令都必须进行错误处理并与异常耦合。如果您的客户端只处理不引发异常的命令,那么您的代码开销就会很大。

这是你不想拥有的东西。因此,您可以将需要错误处理的命令移出命令结构,因为它们似乎与其他命令不同,或者如果您的语言允许,您可以添加仅由关心和抛出的客户端处理的运行时异常需要抛出它们的命令。

另一个极端是每个命令都可能失败,并且客户端有一致的方式来处理错误,这意味着错误不依赖于特定的命令。客户端不必知道哪种命令失败了,它可以以相同的方式处理每个错误。现在您可以让命令接口返回错误状态,客户端可以处理错误。但是处理错误不应该取决于客户端的命令类型。

第二个问题是:命令应该有状态吗?

有些架构中的命令需要状态,而有些架构不需要状态。

决定这一点的一些可能性:

  • 如果你想撤销你的命令,命令需要有一个状态。
  • 如果命令仅用于隐藏对一小组参数起作用的函数的方式,并且结果仅取决于与状态模式相同的命令,则不需要状态,您可以使用相同的对象和结束。

  • 如果您使用该命令在线程之间进行通信,并且您希望将数据从一个线程传输到另一个线程,则该命令需要一个状态。

  • ...如果您认为此列表中应包含某些内容,请发表评论。
于 2009-07-26T19:09:28.403 回答
7

我将参考“Head First Design Patterns”。他们用于命令模式的示例是:

  1. 餐厅场景(客户创建订单,服务员通过向厨房工作人员大喊来调用它,厨房工作人员收到订单)
  2. 遥控场景(人点击按钮,遥控调用命令,设备接收)

显然,在第一种情况下,接收者会产生某种状态:“这里是 grub”,或者“我们没有黑麦面包了”。在高档餐厅中,您可以通过更高级别的异常处理来做到这一点(领班来到餐桌上道歉,提供替代品并准备您的甜点),而服务员只需正确调用命令即可。但是在餐厅里,也许厨师会继续用黑面包代替——服务员(和顾客)需要能够处理这个问题,而不必盯着柜台想“我的黑麦金枪鱼在哪里?” 本书没有直接解决这个问题,但我认为这显然是一个有效的案例。

但在第二种情况下,调用者故意变得愚蠢。如果出现问题,它不会向您显示错误,它根本不会产生任何影响。所有的智能都在客户端来确定它的命令是否及时成功(“废话,我忘了插上它”),或者在接收器中弄清楚要做什么(“播放 CD:关闭 CD 托盘第一的”)。

我不是专家,但我会说将状态返回给调用者对于某些应用程序来说是完全可以的。

于 2009-07-23T06:18:57.577 回答
5

非常好的讨论。我已经研究这个哲学问题好几个小时了,我找到了一个满足我痴迷的解决方案。(我喜欢这个东西的原因是它结合了具体和抽象的逻辑——布尔+设计。)

我简要地考虑过使用异常来返回结果。我放弃了这个想法,因为在许多情况下,它会消除解耦,这是模式本身的核心,正如你们中的一些人所指出的那样。此外,结果通常不是异常,而是标准返回值。我可能会得溃疡。

最终,我编写了一个客户端,它自己实例化一个接收器,将所有逻辑保留在它所属的接收器中。客户端只需调用命令的 execute() 并继续。然后接收者可以调用客户端上的公共方法。没有什么可以退货的。

这是一些示例代码。我没有编写命令类,因为我认为没有它你会明白的。它的 execute() 方法调用接收者的 run() 方法。

客户端:

Class ClientType{

    CommandType m_Command;
    ReceiverType m_Receiver;
    boolean m_bResult;

    ClientType(){

      m_Receiver = new ReceiverType(this);
      m_Command = new CommandType(m_Receiver);
    }

    public void run(){  
            ... 
      m_Command.execute();
    }


    /*  Decoupled from both the   
     *  command and the receiver. 
     *  It's just a public function that
     *  can be called from anywhere. /
    public setResult(boolean bResult){
      m_bResult = bResult;
    }
}

收件人:

Class ReceiverType{

    ClientType m_Client;
    boolean m_bResult;

    ReceiverType(ClientType client){
      m_Client = client;
    }

    public void run(){
            ...
      m_Client.setResult(m_bResult);    
    }
}

乍一看,我似乎违反了解耦要求。但是考虑到客户端对接收器的实现一无所知。接收者知道在客户端调用公共方法这一事实是标准票价。接收者总是知道如何处理他们的参数对象。没有依赖关系。接收者的构造函数采用 ClientType 参数这一事实是无关紧要的。它也可以是任何对象。

我知道这是一个老话题,但希望你们中的一些人能再次加入。如果你看到一个缺陷,请随时让我心碎。这就是我们所做的。

于 2011-03-26T20:14:55.900 回答
4

这绝对是值得商榷的,但它清楚地表明了两种思维方式:

  • 检查是否有问题,然后进行相应的操作
  • 无论如何都要继续,如果发生不好的事情就处理它

我不认为一种方法比另一种更好。例如,在 Java 中,通常最好不要滥用您的异常处理,并在简单地将您的手(和异常)抛在空中之前处理任何可能的问题。使用 Python,更可取的是继续尝试做任何事情,而不管状态代码如何,并让任何异常都得到相应的处理。

您是否希望命令模式返回状态真的取决于您。

于 2009-07-20T17:56:04.660 回答
4

这里的问题可能是该命令将由某个执行程序类执行,该执行程序类将不直接了解该命令的实际作用。

如果我们正在讨论向 execute 方法添加返回类型,则有可能将特定于实现的返回类型暴露给 executor。我的意思是你正在为不同的命令可能有不同的返回值集的情况打开一扇门。如果执行者必须处理这些,那么它将与命令实现更紧密地耦合。

但是,我经常给命令状态 - 允许客户端在构造时为它们配置工作值,然后提供 getter 以允许客户端在完成时提取命令执行的结果。在这种情况下,我可能没有严格遵循命令模式 - 但设计运行良好 - 除非有明确的代码气味 - 这真的是一个问题吗?

注意:也就是说,我很想听听为什么这可能是代码气味的想法。

于 2009-07-20T18:09:15.723 回答
3

正如你的问题所说:

如果命令不成功,调用客户端必须收到状态证明,并最终部署适当的反应。

在这种情况下,我将运行时异常作为状态抛出,其中包含有关它的必要信息。你可以试试。

问候,

于 2009-07-26T18:21:29.140 回答
1

另一个折衷方案是在可能失败的具体命令上公开属性“异常处理程序”。这样,命令的创建者可以处理异常,并且您不会向客户端添加代码开销。当您的大多数命令不应该失败时,这非常有用。

于 2009-07-26T21:26:02.230 回答
0

在我的 CAD/CAM 软件中,包含命令的程序集引用了包含接口和对象层次结构的程序集,这些接口和对象层次结构包含我的软件的各种 UI 元素。它类似于被动视图

命令可以通过视图界面操作 UI 并自行报告任何错误。

基本上它去了

表单实现 IFormInterfaces 并在 EXE 中使用 ScreenViews 注册自己

ScreenObjects 实现 IScreenView 并将自己注册到 ScreenView 程序集以及从命令程序集中获取命令

命令程序集引用 ScreenView 程序集和模型

ScreenView 组件只不过是视图接口的集合,并包含应用程序实现。

于 2009-07-21T12:51:24.167 回答