命令模式返回状态

时间:2009-07-20 17:50:03

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

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

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

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

9 个答案:

答案 0 :(得分:26)

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

这个问题切入了设计模式的核心 - 模板。这不是必须通过书本实现的东西。您确定了一个案例,对书中提供的模式进行逻辑修改会对应用程序有所帮​​助,这一点非常好,特别是在权衡利益和成本之后。

答案 1 :(得分:13)

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

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

您需要考虑的一件事是:

  • 我是否只为某些特定的错误案例添加了更多耦合到许多命令和客户端?

在最糟糕的情况下,您有许多不关心错误的命令,但是一两个命令执行对客户端来说很重要的事情,以确定它是否有效。您现在将已检查的异常添加到接口,因此每个客户端和每个Command都必须执行错误处理并与异常相关联。如果您的客户端只处理没有抛出异常的命令,则代码中会产生很大的开销。

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

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

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

有一些架构,命令需要一个状态,一些命令需要一个状态。

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

  • 如果要对命令执行撤消操作,则命令需要具有状态。
  • 如果命令仅用于隐藏一个函数,该函数对一小组参数起作用,结果只取决于命令的相同状态,如状态模式,则不需要状态而你可以一遍又一遍地使用同一个对象。

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

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

答案 2 :(得分:7)

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

  1. 用餐场景(客户创建订单,等待员工通过厨房工作人员大喊大叫,厨房工作人员收到订单)
  2. 远程控制场景(人员点击按钮,遥控器调用命令,设备接收命令)
  3. 显然,在第一种情况下,某种状态是由接收者产生的:“这里是grub”,或“我们没有黑麦面包”。在一个高档餐厅,你可以通过更高层次的异常处理来做到这一点(maitre d'来到桌面并道歉,提供替代品和compits你的甜点),等待工作人员除了正确调用命令之外什么也不做。但是在一家小餐馆里,也许厨师继续用棕色面包代替 - 等待工作人员(和顾客)需要能够处理,而不是盯着柜台,想知道“我的金枪鱼在哪里是黑麦?”本书没有直接解决这个问题,但我认为这显然是一个有效的案例。

    但是在第二种情况下,调用者故意变得愚蠢。如果出现问题,它不会向你发出错误,它根本就没有效果。所有的智能都在客户端,以确定它的命令是否及时成功(“废话,我忘了插入”),或在接收器中找出该做什么(“播放CD:关闭CD托盘第一“)。

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

答案 3 :(得分:4)

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

  • 检查某些内容是否正常,然后相应地进行
  • 无论如何都要继续并在发生不良事件时处理它

我认为一种方式比另一方式更好。例如,在Java中,通常最好不要滥用您的异常处理并在将手(和例外)丢到空中之前处理任何可能的问题。使用Python,不管状态代码如何,最好继续尝试做任何事情,并且只需相应地处理任何异常。

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

答案 4 :(得分:4)

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

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

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

注意:那就是说,我很想知道为什么这可能是代码味道。

答案 5 :(得分:4)

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

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

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

我知道这是一个旧帖子,但希望你们中的一些人能再次参与其中。如果你看到一个缺陷,请随意打破我的心。这就是我们所做的。

答案 6 :(得分:3)

正如你的问题所说:

  

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

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

的问候,

答案 7 :(得分:1)

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

答案 8 :(得分:0)

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

命令可以通过View Interfaces操纵UI并自行报告任何错误。

基本上就是

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

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

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

ScreenView Assembly只是一个View Interfaces的集合,并且包含了Application Implementation。