命令模式应用的现实世界例子

15

命令模式可用于实现事务行为(和撤消)。
但我在谷歌搜索中无法找到这些的示例。我只能找到一些关于开启关闭灯的微不足道的示例。
在哪里可以找到使用命令模式实现这些行为的编码示例(最好是用Java)?


搜索任何使用 RunnableCallable 的地方。 - jaco0646
4个回答

25
在我们的一个项目中,有以下需求:
  1. 在数据库中创建记录。
  2. 调用一个服务来更新相关记录。
  3. 调用另一个服务来记录票据。
为了以事务性方式执行此操作,每个操作都被实现为带有撤消操作的命令。在每个步骤结束时,将该命令推送到堆栈上。如果某个步骤操作失败,那么我们从堆栈中弹出命令,并对弹出的每个命令调用撤消操作。每个步骤的撤销操作在该命令实现中定义,以撤销之前的 command.execute()。
希望这能帮到您。

1
我不确定我理解了。一个事务被回滚时,并不是由撤销操作组成的。你是如何使用命令模式实现原子部分的? - Jim
1
在数据库级事务中,每个操作都是一个带有撤销和重做操作的命令。在回滚时,DBMS将调用撤销操作来恢复对其持有的DB副本所做的更改。我们正在模拟跨系统的分布式事务中的相同情况。现在明白了吗? - Vikdor
1
撤销对所持有的数据库副本所做的更改。因此,该命令首先适用于数据的副本而不是实际数据?我认为它直接适用于数据,这就是为什么需要“撤消”的原因。您的描述有些清晰,但如果可能的话,我需要更多低级细节来看到整个情况的全貌。 - Jim
不,数据库管理系统会复制数据,以便其他人可以看到您的更改后的数据(事务隔离级别仅与此有关)。当您提交时,副本将更新到实际位置,以便其他人可以看到您的更改。在故障恢复期间,重做操作与检查点一起使用(值得一读的DBMS概念)。 - Vikdor
在您的实现示例中,您复制了哪些数据以模拟事务行为?全部数据吗? - Jim
在分布式事务中,修改是针对实际副本进行的,因此需要特定的实现来确定撤消操作的含义。在我们的情况下,服务调用(步骤2)的撤消操作是发出另一个更新,将记录状态恢复到先前的值,并且对于记录票证的命令,则是取消它。 - Vikdor

3
public final class Ping implements Callable<Boolean> {

  private final InetAddress peer;

  public Ping(final InetAddress peer) {
    this.peer = peer;
  }

  public Boolean call() {
    /* do the ping */
    ...
  }
}
...
final Future<Boolean> result
    = executorService.submit(new Ping(InetAddress.getByName("google.com")));
System.out.println("google.com is " + (result.get() ? "UP" : "DOWN"));

@Jim,维基百科上的模式文章明确提到了以下内容:“一个典型的通用线程池类可能会有一个公共的addTask方法,将工作项添加到等待完成任务的内部任务队列中。...队列中的项目是命令对象。通常这些对象实现了一个通用接口,如java.lang.Runnable,使得线程池可以执行命令,即使线程池类本身没有任何关于它将被用于哪些具体任务的知识。” - obataku
@Jim 这是一个在Java中的例子,除了与ExecutorService结合使用Runnable之外,还使用了Callable。类似的例子可以在关于维基百科文章本身的讨论中看到这里 - obataku
但我正在要求一个具体应用模式的示例。虽然您提供了该模式的一般示例。 - Jim
@Jim 一般来说,任何实现了Runnable接口的类都可以满足命令模式。例如:class ExitTask implements Runnable { public void run() { System.exit(0); } } - obataku
也许我误读了你的回答,但我的问题是,如何使用命令模式(附带Java示例)来实现事务行为。 - Jim
@Jim 确实是这样。我以为你要求一个命令模式的通用Java示例 :-p - obataku

2
命令模式在许多地方都被使用。
1. 当然,你到处都能看到一个非常简单的GUI实现例子,这就是开关。它还广泛用于游戏开发。通过这个模式,用户可以在屏幕上配置他的按钮。 2. 它也被用于网络中,如果需要将命令传递到另一端。 3. 当程序员想要存储用户执行的所有命令时,例如有时候游戏允许你重播整个级别。 4. 它用于实现回调。 5. 这里有一个网站提供了一个回调使用命令模式的例子。 http://www.javaworld.com/article/2077569/core-java/java-tip-68--learn-how-to-implement-the-command-pattern-in-java.html?page=2 6. 这是另一个链接,展示了带有数据库的命令模式。代码是用C#编写的。

0

你必须在Command接口中定义undo()、redo()操作,以及execute()

例如:

interface ChangeI {
    enum State{ READY, DONE, UNDONE, STUCK } ;
    State getState() ;

    void execute() ;    
    void undo() ;    
    void redo() ;
}

在你的ConcreteCommand类中定义一个状态。根据execute()方法执行后的当前状态,你必须决定命令应该被添加到Undo Stack还是Redo Stack,并相应地做出决策。
abstract class AbstractChange implements ChangeI {
    State state = State.READY ;

    public State getState() { return state ; }

    public void execute() {
        assert state == State.READY ;
        try { doHook() ; state = State.DONE ; }
        catch( Failure e ) { state = State.STUCK ; }
        catch( Throwable e ) { assert false ; }
    }

    public void undo() { 
        assert state == State.DONE ; }
        try { undoHook() ; state = State.UNDONE ; }
        catch( Failure e ) { state = State.STUCK ; }
        catch( Throwable e ) { assert false ; }
    }

    public void redo() {
        assert state == State.UNDONE ;
        try { redoHook() ; state = State.DONE ; }
        catch( Failure e ) { state = State.STUCK ; }
        catch( Throwable e ) { assert false ; }
    }

    protected abstract void doHook() throws Failure ; 

    protected abstract void undoHook() throws Failure ; 

    protected void redoHook() throws Failure { doHook() ;} ; 
}

看一下这个撤销-重做命令的文章,以便更好地理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接