2009-07-20 64 views
36

一旦我有关于命令模式的设计讨论。 我的同事说调用.execute()方法后,命令对象不应该返回状态(成功,失败,为什么)。原因是你不应该担心命令是否被执行,因为该命令不能包含任何状态。但是,如果命令具有预期的效果,则必须在调用之后进行检查。他辩称的另一点是,在四人帮中,命令模式不会呈现这种情况(返回状态)。命令模式返回状态

我声称相反的观点。 GoF不提供这种情况,但可以根据您的需求模拟一种模式。如果命令不成功,调用客户端必须接收状态证明,并最终部署适当的响应。通过强制客户检查行动是否取得成功很容易出错并产生重复的代码。此外,在某些情况下,命令会产生结果(例如,将命令添加到情节的命令,将以某种方式将行ID返回给客户端),假装没有状态的命令意味着必须从数据模型中“捞出”新的对象标识符。最后,我们通过不返回状态达成了妥协,但是将新创建的对象的id保留在命令对象中,并且应用程序工作得很好,但我现在也很想知道您的意见。

回答

11

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

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

一个你需要考虑的一件事是:

  • 我是不是增加更多的耦合,许多命令和客户端的只有一些特定的错误情况?

在最糟糕的情况下,您有很多不关心错误的命令,但是有一两条命令对客户端做了一些重要的事情,以确定它是否有效。现在,您将检查到的异常添加到接口中,因此每个客户端和每个命令都必须执行错误处理,并与异常耦合。如果你有一个客户端只处理那些不会抛出异常的命令,那么你的代码会有很大的开销。

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

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

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

有一些体系结构,其中一个命令需要一个状态,另一个体系不需要状态。

一些可能性来决定的:

  • 如果你想为你的命令撤销的命令需要有一个状态。
  • 如果这些命令仅用于隐藏某个函数,该函数只适用于一小组参数,并且结果仅取决于与状态模式相同的命令,则不需要状态,您可以使用一遍又一遍的同一个对象。

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

  • ...如果有什么您认为应该在此列表中留下评论。
4

这绝对是值得商榷的,但它清楚地显示了两种风格的思考:

  • 检查,如果事情是好的,然后进行相应
  • 仍要继续,并处理它如果有什么不好的事情发生

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

确实取决于你是否希望命令模式返回状态。

25

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

这个问题削减了设计模式的核心 - 模板。这不是必须由书实现的东西。您确定了一个案例,其中对本书中介绍的模式进行了逻辑修改将有助于该应用程序,而且这非常好,尤其是在您衡量好处和成本后。

+0

如果托马斯先不回答,我的答案会非常相似。好答案。一个模式是一个指导,而不是一个硬性和快速的规则。 – Odd 2009-07-28 13:44:34

4

难道这里的问题是命令将由一些执行者类执行,它不会直接了解命令实际执行的操作。

如果我们正在讨论的是向执行方法添加返回类型,有一个潜在用于向执行程序公开实现特定的返回类型。我的意思是说,你打开了一扇门,可以看到不同的命令可能有不同的返回值组。如果执行者必须处理这些问题,那么它将与命令实现更加紧密地耦合。但是,我经常给出命令状态 - 允许它们在构造时由客户端配置工作值,然后提供getter来允许客户端在完成时提取命令执行的结果。在这种情况下,我可能没有严格遵守命令模式 - 但设计运行良好 - 除非有明确的代码味道 - 这真的是一个问题吗?

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

+1

还有一个理由让一个州成为命令。当你想撤消它们时,他们必须知道如何行动。尽管这本身就是一件很麻烦的事情,但是当你撤销创建的行时,命令必须记住它创建的id。正如我再说一遍,因为你不能保证这个ID仍然存在(意思是你仍然可以拥有这个对象,但是它的ID改变了),所以我会重复这个地雷。 – 2009-07-20 18:22:41

+1

我想上面的第二段是这个问题的关键。这种模式的初衷是有一些对象执行命令,但不知道它们实际做了什么。问题是:执行者是否需要了解一些非特定的命令状态(如通过,失败等)?如果是这样,请添加返回类型,如果不是,则不要。 – Steg 2009-07-28 08:43:24

+0

我同意teabot的用法,如果你需要有状态的命令来添加返回类型。 – JamesC 2011-08-01 15:19:44

0

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

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

基本上不言而喻

形式实现IFormInterfaces并在EXE与屏幕浏览自己注册

ScreenObjects实施IScreenView并与屏幕视图组件以及从命令组件

命令抢命令注册自己组件参考ScreenView组件和模型ScreenView组件不仅仅是视图接口和保持组件的集合应用程序实现。

7

我会参考“首先设计模式”。他们使用的命令模式的例子是:

  1. 餐车方案(客户创造秩序,等待工作人员通过在厨房工作人员喊叫调用它,厨房工作人员接收订单)
  2. 远程控制方案(人点击一个按钮,遥控器调用命令和设备接收它)
  3. 在第一种情况下

显然,某种状态的由接收器产生的:“这里的平头”,或“我们'黑麦面包出来“。在一家高级餐厅中,您可以通过更高层次的异常处理来做到这一点(maitre d'来到餐桌上并道歉,提供替代品并且补充您的甜点),而等待的员工只需正确调用命令就不需要做任何事情。但是在一个小餐馆里,也许厨师继续前进,代替棕色面包 - 等待工作人员(和客户)需要能够处理这个问题,而不用盯着柜台想知道“我的金枪鱼在黑麦的哪里?”这本书没有直接解决,但我认为这显然是一个有效的案例。

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

我不是专家,但我会说,返回到调用者的状态对于某些应用程序来说完全没问题。

3

正如你的问题说:

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

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

问候,

1

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

4

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

我简要地考虑过使用异常来返回结果。我放弃了这个想法,因为在许多情况下,它会消除脱钩,这是模式本身的核心,正如一些人已经注意到的那样。另外,结果通常不是一个异常,而是一个标准的返回值。我可能会溃疡。最终,我写了一个客户端,用它自己实例化一个接收者,将接收者的所有逻辑保留在它所属的地方。客户端只是调用该命令的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参数这一事实是无关紧要的。它也可以是任何对象。

我知道这是一个古老的线程,但希望你们中的一些人再次参加。如果您发现缺陷,请随时打破我的心。这就是我们所做的。