【问题标题】:Using Command Design pattern使用命令设计模式
【发布时间】:2011-01-02 04:48:28
【问题描述】:

谁能用一个简单的例子来解释命令模式?我尝试在互联网上搜索,但我很困惑。

【问题讨论】:

标签: java oop design-patterns command-pattern


【解决方案1】:
public interface Command {
   public void execute();
}

在大多数情况下,命令是不可变的,并且包含封装了按需执行的单个操作的指令。您可能还有一个 RuntimeCommand 在执行时接受指令,但这会根据实现更深入地研究策略或装饰器模式。

在我自己看来,我认为注意命令的不可变上下文非常重要,否则命令就会变成建议。例如:

public final class StopServerCommand implements Command {
    private final Server server;

    public StopServerCommand(Server server) { this.server = server; }

    public void execute() {
        if(server.isRunning()) server.stop();
    }
}

public class Application {
    //...
    public void someMethod() {
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(Event e) {
                 stopCommand.execute();
            }
        });
    }
}

我个人不太喜欢命令。根据我自己的经验,它们只适用于框架回调。

如果有帮助,请以隐喻的方式思考命令;受过训练的士兵由他/她的指挥官下达命令,并根据要求士兵执行此命令。

【讨论】:

  • @Droo,跟进你的“不太喜欢”——它们实际上是抽象设备控制之类的非常自然的实现。例如,我过去必须使用一个通用的操纵杆来驱动各种相机(它们都使用不同的串行协议)。有一个用于“平移”的命令、另一个用于“缩放”等的命令非常有帮助。
  • @Droo:你能用简单的例子解释一下吗?
  • @Droo 对象stopCommand 到底是在哪里创建的?
【解决方案2】:

我的要求是执行一系列任务(可以在多个用例中重复使用),每个任务都有自己的异常流。在这里找到了命令模式的实现逻辑。

我试图使它像命令执行的每个操作(无论是正常/备用流程)也可以是异常处理程序。但是,如果该命令已注册到另一个处理程序,则应该使用它。欢迎任何改进/纠正的建议。

public interface Command {
    Result run() throws Exception;
    Command onException(ExceptionHandler handler);
}

public class Result {
}

public interface ExceptionHandler {
    void handleException(Exception e);
}

public interface Action {
    Result execute() throws Exception;
}

public class BasicCommand implements Command {
private Action action;
private ExceptionHandler handler;

public BasicCommand(Action action) {
    if (action == null) {
        throw new IllegalArgumentException("Action must not be null.");
    }
    this.action = action;
    this.handler = (ExceptionHandler) this.action;
}

@Override
public Command onException(ExceptionHandler handler) {
    if (handler != null) {
        this.handler = handler;
    }
    return this;
}

public Result run() throws Exception {
    Result result = null;
    try {
        result = action.execute();
    } catch (Exception e) {
        handler.handleException(e);
    }
    return result;
}

}

public class BasicAction implements Action, ExceptionHandler {
    private Object[] params;


    public BasicAction(Object... params) {
        this.params = params;
    }

    @Override
    public Result execute() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void handleException(Exception e) {
        // TODO exception translation: prepare unchecked application. exception and throw..
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        int param1 = 10;
        String param2 = "hello";

        // command will use the action itself as an exception handler
        Result result = new BasicCommand(new BasicAction(param1, param2)).run();

        ExceptionHandler myHandler = new ExceptionHandler(){
            @Override
            public void handleException(Exception e) {
                System.out.println("handled by external handler");
            }
        };
        // command with an exception handler passed from outside.
          Result result2 = new BasicCommand(new BasicAction(param1, param2)).onException(myHandler).run();

    }
}

【讨论】:

    【解决方案3】:

    这是另一个示例,您可以使用现实生活场景来了解命令模式的工作原理:如果不使用命令模式,您将无法乘飞机从一个地方旅行到另一个地方!

    如果您经常出差,作为客户,您所关心的只是从您所在的地方旅行到另一个地方。您不关心飞行员将如何驾驶飞机或哪家航空公司将可用..您无法真正预测到这一点。你想要的只是去机场并告诉他们带你去你的目的地。

    但如果你这样做,你对机场当局的指挥会被嘲笑!他们需要你提供一个命令对象,这是你的票。尽管您不关心哪家航空公司或哪种飞机类型,但当您准备好飞行时,您需要提供票务命令对象。调用者,即机场官员需要检查您的命令(机票),以便他们可以验证它,如果它是假的则撤消它,如果他们犯了错误则重做它(您不必重新完成预订过程) .

    简而言之,他们希望在决定是否调用或执行您的命令之前完全控制您的命令(票证),这让航空公司(接收者)执行(让您上飞机并带您到您的目的地) 。

    请注意,您的命令(您的机票)已经拥有接收方(航空公司)的信息,没有这些信息,机场官员甚至不会首先开始处理您的机票。

    机场当局甚至可能有一堆他们正在处理的机票。他们可能会选择延迟我的票,让在我之后的人通过(在我之前调用另一个人的票)

    代码如下:

     [TestClass]
        public class Client
        {
            [TestMethod]
            public void MyFlight_UsingCommandPattern()
            {
                var canadianAirline = new Airline();
    
                AirlineTicket_Command myTicket = new MyAirLineTicket(canadianAirline);
    
                var airportOfficials = new AirportOfficials_Invoker(myTicket);
                airportOfficials.ProcessPasengerTicket_And_AllowPassengerToFly_Execute();
    
                //assert not implemented
            }
        }
    
        public class AirportOfficials_Invoker
        {
            private AirlineTicket_Command PassengerTicket { set; get; }
    
            public AirportOfficials_Invoker(AirlineTicket_Command passengerTicket)
            {
                throw new NotImplementedException();
            }
    
            public void ProcessPasengerTicket_And_AllowPassengerToFly_Execute()
            {
                PassengerTicket.Execute();
            }
        }
    
        public abstract class AirlineTicket_Command
        {
            protected Airline Airline { set; get; }
    
            protected AirlineTicket_Command(Airline airline)
            {
                Airline = airline;
            }
    
            public abstract void Execute();
        }
    
        public class MyAirLineTicket : AirlineTicket_Command
        {
            public MyAirLineTicket(Airline airline)
                : base(airline)
            {
            }
    
            public override void Execute()
            {
                Airline.FlyPassenger_Action();
            }
        }
    
        public class Airline
        {
            public void FlyPassenger_Action()
            {
    //this will contain all those stuffs of getting on the plane and flying you to your destination
            }
        }
    

    【讨论】:

      【解决方案4】:

      您可以将命令模式的工作流程想象如下。

      Client 调用 Invoker => Invoker 调用 ConcreteCommand => ConcreteCommand 调用Receiver方法,实现抽象Command方法。

      UML 图来自dofactory 文章:

      主要特点:

      1. Command 为所有命令声明一个接口,提供一个简单的 execute() 方法,该方法要求命令的接收者执行操作。

      2. 接收方知道如何执行请求。

      3. Invoker持有命令,可以通过调用execute方法获取Command执行请求。

      4. Client 创建 ConcreteCommands 并为命令设置 Receiver

      5. ConcreteCommand 定义了动作和接收者之间的绑定。

      6. Invoker 调用执行时,ConcreteCommand 将在 Receiver 上运行一个或多个操作。

      代码sn-p:

      interface Command {
          void execute();
      }
      interface Receiver {
          public  void switchOn();
      
      }
      class OnCommand implements Command{
          private Receiver receiver;
      
          public OnCommand(Receiver receiver){
              this.receiver = receiver;
          }
          public void execute(){
              receiver.switchOn();
          }
      }
      class Invoker {
          private Command command;
      
          public Invoker(Command command){
              this.command = command;
          }
          public void execute(){
              this.command.execute();
          }
      }
      
      class TV implements Receiver{
      
           public void switchOn(){
              System.out.println("Switch on from TV");
          }
      }
      class DVDPlayer implements Receiver{
      
          public void switchOn(){
               System.out.println("Switch on from DVDPlayer");
          }
      }
      
      public class CommandDemoEx{
          public static void main(String args[]){
              // On command for TV with same invoker 
              Receiver receiver = new TV();
              Command onCommand = new OnCommand(receiver);
              Invoker invoker = new Invoker(onCommand);
              invoker.execute();
      
              // On command for DVDPlayer with same invoker 
              receiver = new DVDPlayer();
              onCommand = new OnCommand(receiver);
              invoker = new Invoker(onCommand);
              invoker.execute();            
          }
      }
      

      输出:

      Switch on from TV
      Switch on from DVDPlayer
      

      解释:

      在这个例子中,

      1. Command 接口定义了execute() 方法。
      2. OnCommandConcreteCommand,它实现了execute() 方法。
      3. Receiver 是一个接口,实现者必须提供方法的实现。
      4. TVDVDPlayer 是两种类型的 Receiver,它们像 OnCommand 一样传递给 ConcreteCommand。
      5. Invoker 包含 Command。这是将 Sender 与 Receiver 分离的关键。
      6. Invoker 接收 OnCommand -> 调用 Receiver (TV) 来执行此命令。

      通过使用 Invoker,您可以打开 TV 和 DVDPlayer。如果你扩展这个程序,你也关闭了电视和 DVDPlayer。

      你可以使用Command模式来

      1. 解耦命令的发送者和接收者

      2. 实现回调机制

      3. 实现撤消和重做功能

      4. 维护命令历史记录

      看看这篇 dzonejournaldevWikipedia 文章。

      作为 Wikipedia 页面的源代码简单、清晰且一目了然。

      如果您按照article中引用的步骤进行操作,您可以实现撤消重做

      【讨论】:

      • 你在这里写的Key features: 非常有用至少对我来说,你有/知道任何博客或其他东西,所以我可以找到其他设计模式的解释? 我的意思是像你在这里做的那样
      • 你可以找到我关于上述格式的设计模式的其他帖子。
      【解决方案5】:

      命令设计模式将服务调用者和服务提供者解耦。在一般情况下,例如,如果Object A 想要Object B 的服务,它将直接调用B.requiredService()。因此,A 知道 B。在命令模式中,这种耦合被移除。这里有一个称为Command 的中间对象,它出现在图片中。因此,A 处理Command 对象,而命令对象处理实际对象B。这种方法有几个应用程序,例如设计应用程序,它们是:-

      • 接受命令作为请求。
      • 撤消请求。
      • 请求请求。
      • 创建宏。
      • 创建任务执行器和任务管理器。

      有关命令设计模式的更多信息,我会推荐https://en.wikipedia.org/wiki/Command_pattern。 对于所有其他设计模式,请参阅https://www.u-cursos.cl/usuario/.../mi_blog/r/head_first_design_patterns.pdf

      【讨论】:

        【解决方案6】:

        我想在这里给你另一个粗略的类比。

        假设有一天上帝召唤你并告诉你世界处于危险之中,他需要你的帮助来拯救它。进一步帮助你,他告诉你他已经在地球上派了一些超级英雄。

        因为他不知道哎呀,因此他不称他们为超级英雄(不为您提供任何接口或抽象类),而只是告诉您他们的前任 - 蝙蝠侠的名字,超人,​​钢铁侠和他们拥有的力量。

        他还说将来他可能会派出更多这样的人。

        现在他分配给你特殊的责任 -> 控制它们,并为你提供七只手。他不会自己解决每只手的任务,而是把它留给你。

        您希望灵活地为任何超级英雄的力量分配任何手动控制,并且不希望在多个条件下反复改变事物。

        您正在修复中。你现在做什么?

        输入命令模式。

        创建一个接口Command,其中只有一个方法execute()。 封装每个超级英雄的每一个力量,并为 ex - IronManCreatesSuitCommand 实现 Command

        现在您可以随时将任何手分配给任何命令,从而为您提供更大的灵活性,因为现在您的任何一只手都不关心它必须执行的特定任务。您只需为其分配任何命令即可。它在其上调用执行,该命令会处理其他所有事情。

        现在,即使上帝派了任何其他具有不同力量的超级英雄,你也知道该怎么做。

        【讨论】: