【问题标题】:command pattern returning status命令模式返回状态
【发布时间】:2010-11-12 09:36:16
【问题描述】:

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

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

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

【问题讨论】:

    标签: design-patterns language-agnostic command-pattern


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 如果 Thomas 没有先回答,我的回答会与此非常相似。好答案。模式是一种指导,而不是硬性规定。
      【解决方案3】:

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

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

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

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

      【讨论】:

      • 给命令一个状态还有另一个原因。当您想撤消它们时,它们必须知道如何采取行动。虽然这本身就是一件事情,充满了麻烦,但是当你撤消创建一行时,命令必须记住它创建了哪个id。正如我重复的那样,充满了地雷,因为不能保证您的 id 仍然存在(这意味着您仍然可以拥有该对象,但它的 id 已更改)。
      • 我认为上面的第二段是这里问题的症结所在。这种模式的初衷是有一些对象执行命令但不知道它们实际做什么。那么问题来了:执行者是否需要了解一些非特定的命令状态(例如通过、失败等)?如果是,则添加返回类型,如果不是,则不要。
      • 我同意teabot 的用法,即如果您需要有状态命令的某些内容,则添加返回类型。
      【解决方案4】:

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

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

      基本上是这样

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

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

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

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

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        【讨论】:

          【解决方案6】:

          正如你的问题所说:

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

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

          问候,

          【讨论】:

          • +1。从未见过返回状态的命令模式实现
          【解决方案7】:

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

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

          您需要考虑的一件事是:

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

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

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

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

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

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

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

          • 如果您想撤销命令,则命令需要有一个状态。
          • 1234563可以一遍又一遍地使用同一个对象。
          • 如果您使用该命令在线程之间进行通信,并且您希望将数据从一个线程传输到另一个线程,则该命令需要一个状态。

          • ...如果您认为此列表中应包含某些内容,请发表评论。

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              【解决方案9】:

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

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

              最终,我编写了一个客户端,它自己实例化一个接收器,将所有逻辑保留在它所属的接收器中。客户端只需调用命令的 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 参数这一事实是无关紧要的。它也可以是任何对象。

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

              【讨论】:

              • 这不是一个糟糕的设计。我唯一不喜欢的是设置结果并不是命令的严格部分。相反,它是客户端的一部分,实际上接近于注册回调函数。在某些情况下,这可能难以跟踪实际调用 setResult 函数的命令,以及执行历史和结果设置,但在某些情况下它可能工作得很好。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-11-28
              • 2015-09-09
              • 2011-06-28
              • 2016-12-16
              • 2021-02-15
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多