命令模式返回状态

Ste*_*ini 38 language-agnostic design-patterns command-pattern

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

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

最后,我们通过不返回状态但保持命令对象中新创建的对象的id来达成妥协,并且应用程序仍然运行良好,但我现在很想知道您的意见.

Tho*_*ens 26

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

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


Jan*_*usz 13

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

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

您需要考虑的一件事是:

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

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

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

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

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

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

决定这个的一些可能性:

  • 如果要对命令执行撤消操作,则命令需要具有状态.
  • 如果命令仅用于隐藏一个函数,该函数适用于一小组参数,并且结果仅取决于与状态模式相同的命令,则不需要状态,并且可以使用相同的对象并结束.

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

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


Zac*_*son 7

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

  1. 用餐场景(客户创建订单,等待工作人员通过厨房员工喊叫,厨房工作人员收到订单)
  2. 远程控制场景(人员点击按钮,遥控器调用命令,设备接收命令)

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

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

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


小智 5

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

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

最终,我编写了一个客户端,它使用自身实例化接收器,将所有逻辑保留在它所属的接收器中。客户端只是调用命令的 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

收件人:

Class ReceiverType{

    ClientType m_Client;
    boolean m_bResult;

    ReceiverType(ClientType client){
      m_Client = client;
    }

    public void run(){
            ...
      m_Client.setResult(m_bResult);    
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这不是一个糟糕的设计。我唯一不喜欢的是设置结果严格来说并不是命令的一部分。它是客户端的一部分,实际上接近于注册回调函数。在某些情况下,这可能很难跟踪哪个命令实际调用了 setResult 函数,以及执行历史和结果设置,但在某些情况下它可能工作得很好。 (2认同)