Saturday, July 26, 2014

Command Design Pattern



  • Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
  • Promote "invocation of a method on an object" to full object status
  • An object-oriented callback
Command decouples the object that invokes the operation from the one that knows how to perform it.

 
                        图4  命令模式组装过程的调用顺序示意图 
接下来再看看真正执行命令时的调用顺序示意图,如图5所示: 

 

http://java.dzone.com/articles/design-patterns-command
Command declares an interface for all commands, providing a simple execute() method which asks the Receiver of the command to carry out an operation. The Receiver has the knowledge of what to do to carry out the request. The Invoker holds a command and can get the Command to execute a request by calling the execute method. The Client creates ConcreteCommands and sets a Receiver for the command. The ConcreteCommand defines a binding between the action and the receiver. When the Invoker calls execute the ConcreteCommand will run one or more actions on the Receiver.

Invoker: 
        要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。 
Client: 
        创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
        这里并没有调用执行的代码
可以用不同的命令对象,去参数化配置客户的请求
可撤销操作的意思就是:放弃该操作,回到未执行该操作前的状态.
有两种基本的思路来实现可撤销的操作,一种是补偿式,又称反操作式:比如被撤销的操作是加的功能,那撤消的实现就变成减的功能;同理被撤销的操作是打开的功能,那么撤销的实现就变成关闭的功能。
        另外一种方式是存储恢复式,意思就是把操作前的状态记录下来,然后要撤销操作的时候就直接恢复回去就可以了。
  1. public interface Command {  
  2.     public void execute();   
  3.     public void undo();  
  4. }  

  1. private List<Command> undoCmds = new ArrayList<Command>();  
什么时候向命令的历史记录里面加值呢?        很简单,答案是在每个操作按钮被按下的时候,也就是你操作加法按钮或者减法按钮的时候.
  1. private List<Command> redoCmds = new ArrayList<Command>();  
那么什么时候向这个集合里面赋值呢?大家要注意,恢复的命令数据是来源于撤销的命令,也就是说有撤销才会有恢复,所以在撤销的时候向这个集合里面赋值,注意要在撤销的命令被删除前赋值.
  1.     public void addPressed(){  
  2.         this.addCmd.execute();  
  3.         //把操作记录到历史记录里面  
  4.         undoCmds.add(this.addCmd);  
  5.     }  
  6.     public void substractPressed(){  
  7.         this.substractCmd.execute();  
  8.         //把操作记录到历史记录里面  
  9.         undoCmds.add(this.substractCmd);  
  10.     }  
  11.     public void undoPressed(){  
  12.         if(this.undoCmds.size()>0){  
  13.             //取出最后一个命令来撤销  
  14.             Command cmd = this.undoCmds.get(undoCmds.size()-1);  
  15.             cmd.undo();  
  16.             //如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面  
  17.             this.redoCmds.add(cmd );  
  18.             //然后把最后一个命令删除掉,  
  19.             this.undoCmds.remove(cmd);  
  20.         }else{  
  21.             System.out.println("很抱歉,没有可撤销的命令");  
  22.         }  
  23.     }  
  24.     public void redoPressed(){  
  25.         if(this.redoCmds.size()>0){  
  26.             //取出最后一个命令来重做  
  27.             Command cmd = this.redoCmds.get(redoCmds.size()-1);  
  28.             cmd.execute();        
  29.             //把这个命令记录到可撤销的历史记录里面  
  30.             this.undoCmds.add(cmd);  
  31.             //然后把最后一个命令删除掉  
  32.             this.redoCmds.remove(cmd);  
  33.         }else{  
  34.             System.out.println("很抱歉,没有可恢复的命令");  
  35.         }  
  36.     }  
宏命令
  1. public class MenuCommand implements Command {  
  2.     /** 
  3.      * 用来记录组合本菜单的多道菜品,也就是多个命令对象 
  4.      */  
  5.     private Collection<Command> col = new ArrayList<Command>();  
  6.     /** 
  7.      * 点菜,把菜品加入到菜单中 
  8.      * @param cmd 客户点的菜 
  9.      */  
  10.     public void addCommand(Command cmd){  
  11.         col.add(cmd);  
  12.     }  
  13.     public void execute() {  
  14.         //执行菜单其实就是循环执行菜单里面的每个菜  
  15.         for(Command cmd : col){  
  16.             cmd.execute();  
  17.         }  
  18.     }  

  1. public class Waiter {  
  2.     /** 
  3.      * 持有一个宏命令对象——菜单 
  4.      */  
  5.     private MenuCommand menuCommand = new MenuCommand();  
  6.     /** 
  7.      * 客户点菜 
  8.      * @param cmd 客户点的菜,每道菜是一个命令对象 
  9.      */  
  10.     public void orderDish(Command cmd){  
  11.         //客户传过来的命令对象是没有和接收者组装的  
  12.         //在这里组装吧  
  13.         CookApi hotCook = new HotCook();  
  14.         CookApi coolCook = new CoolCook();  
  15.         //判读到底是组合凉菜师傅还是热菜师傅  
  16.         //简单点根据命令的原始对象的类型来判断  
  17.         if(cmd instanceof DuckCommand){  
  18.             ((DuckCommand)cmd).setCookApi(hotCook);  
  19.         }else if(cmd instanceof ChopCommand){  
  20.             ((ChopCommand)cmd).setCookApi(hotCook);  
  21.         }else if(cmd instanceof PorkCommand){  
  22.             //这是个凉菜,所以要组合凉菜的师傅  
  23.             ((PorkCommand)cmd).setCookApi(coolCook);  
  24.         }  
  25.         //添加到菜单中  
  26.         menuCommand.addCommand(cmd);  
  27.     }  
  28.     /** 
  29.      * 客户点菜完毕,表示要执行命令了,这里就是执行菜单这个组合命令 
  30.      */  
  31.     public void orderOver(){  
  32.         this.menuCommand.execute();  
  33.     }  
  34. }

  1.     public static void main(String[] args) {  
  2.         //客户只是负责向服务员点菜就好了  
  3.         //创建服务员  
  4.         Waiter waiter = new Waiter();  
  5.           
  6.         //创建命令对象,就是要点的菜  
  7.         Command chop = new ChopCommand();  
  8.         Command duck = new DuckCommand();  
  9.         Command pork = new PorkCommand();  
  10.           
  11.         //点菜,就是把这些菜让服务员记录下来  
  12.         waiter.orderDish(chop);  
  13.         waiter.orderDish(duck);  
  14.         waiter.orderDish(pork);  
  15.           
  16.         //点菜完毕  
  17.         waiter.orderOver();  
  18.     }
http://sishuok.com/forum/blogPost/list/100.html
队列请求,就是对命令对象进行排队,组成工作队列,然后依次取出命令对象来执行。多用多线程或者线程池来进行命令队列的处理,当然也可以不用多线程,就是一个线程,一个命令一个命令的循环处理,就是慢点。
后厨有很多厨师,每个厨师都从这个命令队列里面取出一个命令,然后按照命令做出菜来,就相当于多个线程在同时处理一个队列请求.
  1. public class CommandQueue {  
  2.     /** 
  3. * 用来存储命令对象的队列 
  4. */  
  5.     private static List<Command> cmds = new ArrayList<Command>();  
  6.     /** 
  7.      * 服务员传过来一个新的菜单,需要同步, 
  8.      * 因为同时会有很多的服务员传入菜单,而同时又有很多厨师在从队列里取值 
  9.      * @param menu 传入的菜单 
  10.      */  
  11.     public  synchronized  static void addMenu(MenuCommand menu){  
  12.         //一个菜单对象包含很多命令对象  
  13.         for(Command cmd : menu.getCommands()){  
  14.             cmds.add(cmd);  
  15.         }  
  16.     }  
  17.     /** 
  18.      * 厨师从命令队列里面获取命令对象进行处理,也是需要同步的 
  19.      */  
  20.     public   synchronized   static Command getOneCommand(){  
  21.         Command cmd = null;  
  22.         if(cmds.size() > 0 ){  
  23.             //取出队列的第一个,因为是约定的按照加入的先后来处理  
  24.             cmd = cmds.get(0);  
  25.             //同时从队列里面取掉这个命令对象  
  26.             cmds.remove(0);  
  27.         }  
  28.         return cmd;  
  29.     }  

  1. public class MenuCommand implements Command {
  2.     private Collection<Command> col = new ArrayList<Command>();  
  3.     public void addCommand(Command cmd){  
  4.         col.add(cmd);  
  5.     }    
  6.     public void execute() {  
  7.         //执行菜单就是把菜单传递给后厨  
  8.         CommandQueue.addMenu(this);  
  9.     }  
  10. }  

  1. public class CookManager {  
  2.     private static boolean runFlag = false;  
  3.     public static void runCookManager(){  
  4.         if(!runFlag){  
  5.             runFlag = true;  
  6.             //创建三位厨师  
  7.             HotCook cook1 = new HotCook("张三");  
  8.             HotCook cook2 = new HotCook("李四");  
  9.             HotCook cook3 = new HotCook("王五");  
  10.   
  11.             //启动他们的线程  
  12.             Thread t1 = new Thread(cook1);  
  13.             t1.start();  
  14.             Thread t2 = new Thread(cook2);  
  15.             t2.start();  
  16.             Thread t3 = new Thread(cook3);  
  17.             t3.start();  
  18.         }  
  19.     }  

  1. public class HotCook implements CookApi,Runnable{      
  2.     public void cook(int tableNum,String name) {  

  3.     }  
  4.     public void run() {  
  5.         while(true){  
  6.             //到命令队列里面获取命令对象  
  7.             Command cmd = CommandQueue.getOneCommand();  
  8.             if(cmd != null){  
  9.                 //说明取到命令对象了,这个命令对象还没有设置接收者  
  10.                 //因为前面都还不知道到底哪一个厨师来真正执行这个命令  
  11.                 //现在知道了,就是当前厨师实例,设置到命令对象里面  
  12.                 cmd.setCookApi(this);  
  13.                 //然后真正执行这个命令  
  14.                 cmd.execute();  
  15.             }  
  16.             //休息1秒  
  17.             try {  
  18.                 Thread.sleep(1000L);  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     }  
  24. }  

退化的命令模式
如果命令的实现对象超级智能,实现了命令所要求的功能,那么就不需要接收者了,既然没有了接收者,那么也就不需要组装者了。

把单独实现命令接口的类改成用匿名内部类实现,这个时候就只剩下命令的接口、Invoker类,还有客户端了。
When Would I Use This Pattern?
The Command Pattern is useful when:

A history of requests is needed
You need callback functionality
Requests need to be handled at variant times or in variant orders
The invoker should be decoupled from the object handling the invocation.

RemoteControl control = new RemoteControl();
Light light = new Light();
Command lightsOn = new LightsOnCommand(light);
control.setCommand(lightsOn);
control.pressButton();


Important Points on Command Pattern

  • Command pattern helps to decouple the invoker and the receiver. Receiver is the one which knows how to perform an action.
  • Command helps to implement call back in java.
  • Helps in terms of extensibility as we can add new command without changing existing code.
  • Command defines the binding between receiver and action.
  • A command should be able to implement undo and redo operations. That is restting the state of the receiver. It can be done from the support of receiver.

Command Pattern in JDK

Implementations of java.lang.Runnable and javax.swing.Action follows command design pattern.

Command dp encapsulates method invocation.
The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.

A command object encapsulates a request by binding together a set of actions on a specific receiver.
To achieve this, it packages the actions and the receiver up into an object that exposes just one method, execute(). When called, execute() causes the actions to be invoked on the receiver. From the outside, no other objects really know what actions get performed on what receiver; they just know that if they call the execute() method, their request will be serviced.

The Command Pattern allows you to decouple the requester of an action from the object that actually performs the action. So, here the requester would be the remote control and the object that performs the action would be an instance of one of your vendor classes.

A command object encapsulates a request to do something on a specific object. So, if we store a command object for each button, when the button is pressed we ask the command object to do some work. The remote doesn’t have any idea what the work is, it just has a command object that knows how to talk to the right object to get the work done. 


Client creates the concrete command object and sets its receiver, and pass the command object to the invoker(may be via network)
The actions and the receiver are bound in the command object.
AT some point, the invoker calls the command object's execute method which results in the actions being invoked on the receiver.
The execute invokes the actions on the receiver to fulfill the request.

The invoker makes a request by calling execute on the ConcreteCommand.

Undo
When commands support undo, they have an undo() method that mirrors the execute() method. Whatever execute() last did, undo() reverses. 
Using state to implement Undo: CeilingFanCommand

Examples
Order in Restaurant(Customer,Waitress,Cook, Order)
Remote controls with on/off for different devices

Command, LightOnCommand, 
http://bylijinnan.iteye.com/blog/1692014
  1.  * 命令模式的“可取消的操作” 
  2.  * 实现方法又分两种 
  3.  * 一种是补偿法,例如操作是加法,那取消操作就执行减法 
  4.  * 另一种是存储法(具体参见备忘录模式) 
  5.  * 下面代码实现补偿法: 
  6.  * 两个命令对象:AddOperationCommand、SubstractOperationCommand 
  7.  * 这两个命令各自都有两个方法:执行和撤销 
  8.  */  
  9. interface IOperation {  
  10.       
  11.     int getResult();  
  12.       
  13.     void setResult(int result);  
  14.       
  15.     void add(int i);  
  16.       
  17.     void substract(int j);  
  18.       
  19. }  
  20.   
  21.   
  22. //Receiver  
  23. class Operation implements IOperation {  
  24.   
  25.     private int result;  
  26.       
  27.     public void add(int i) {  
  28.         result += i;  
  29.     }  
  30.   
  31.     public int getResult() {  
  32.         return result;  
  33.     }  
  34.   
  35.     public void setResult(int result) {  
  36.         this.result = result;  
  37.     }  
  38.   
  39.     public void substract(int j) {  
  40.         result -=j;  
  41.     }  
  42.       
  43. }  
  44.   
  45.   
  46. interface IOperationCommand {  
  47.       
  48.     public void execute();  
  49.       
  50.     public void undo();  
  51.       
  52. }  
  53.   
  54.   
  55. class AddOperationCommand implements IOperationCommand {  
  56.   
  57.     private IOperation operation;  
  58.       
  59.     private int opeNum;  
  60.       
  61.     public AddOperationCommand(IOperation operation, int opeNum) {  
  62.         this.operation = operation;  
  63.         this.opeNum = opeNum;  
  64.     }  
  65.       
  66.     public void execute() {  
  67.         operation.add(opeNum);  
  68.     }  
  69.   
  70.     public void undo() {  
  71.         operation.substract(opeNum);  
  72.     }  
  73.       
  74. }  
  75.   
  76.   
  77. class SubstractOperationCommand implements IOperationCommand {  
  78.       
  79.     private IOperation operation;  
  80.       
  81.     private int opeNum;  
  82.       
  83.     public SubstractOperationCommand(IOperation operation, int opeNum) {  
  84.         this.operation = operation;  
  85.         this.opeNum = opeNum;  
  86.     }  
  87.       
  88.     public void execute() {  
  89.         operation.substract(opeNum);  
  90.     }  
  91.       
  92.     public void undo() {  
  93.         operation.add(opeNum);  
  94.     }  
  95.       
  96. }  
  97.   
  98.   
  99. //Invoker  
  100. class Calculator {  
  101.       
  102.     private IOperationCommand addCommand;  
  103.       
  104.     private IOperationCommand substractCommand;  
  105.       
  106.     private List<IOperationCommand> undoCommands = new ArrayList<IOperationCommand>();  
  107.       
  108.     private List<IOperationCommand> redoCommands = new ArrayList<IOperationCommand>();  
  109.       
  110.     public void setAddCommand(IOperationCommand addCommand) {  
  111.         this.addCommand = addCommand;  
  112.     }  
  113.       
  114.     public void setSubstarctCommanc(IOperationCommand substractCommand) {  
  115.         this.substractCommand = substractCommand;  
  116.     }  
  117.       
  118.     public void add() {  
  119.         addCommand.execute();  
  120.         undoCommands.add(addCommand);  
  121.     }  
  122.   
  123.     public void substract() {  
  124.         substractCommand.execute();  
  125.         undoCommands.add(substractCommand);  
  126.     }  
  127.       
  128.     public void undo() {  
  129.         if (undoCommands.size() > 0) {  
  130.             IOperationCommand command = undoCommands.get(undoCommands.size() - 1);  
  131.             command.undo();  
  132.             redoCommands.add(command);  
  133.             undoCommands.remove(command);  
  134.         } else {  
  135.             System.out.println("nothing to undo.");  
  136.         }  
  137.     }  
  138.       
  139.     public void redo() {  
  140.         if (redoCommands.size() > 0) {  
  141.             IOperationCommand command = redoCommands.get(redoCommands.size() - 1);  
  142.             command.execute();  
  143.             undoCommands.add(command);  
  144.             redoCommands.remove(command);  
  145.         } else {  
  146.             System.out.println("nothing to redo.");  
  147.         }  
  148.     }  
  149. }
  1.  * 命令模式的“宏命令” 
  2.  * 所谓宏命令就是有一个宏命令对象,这个命令对象会触发一系列的命令对象 
  3.  * 书上举了一个点菜的例子 
  1. //服务员 Invoker + Client  
  2. class Waiter {  
  3.       
  4.     private MenuCommand menuCommand = new MenuCommand();  
  5.       
  6.     public void orderDish(ICookCommand cmd) {  
  7.         //组装-根据菜式分发给不同的厨师  
  8.         ICook hotCook = new HotCook();  
  9.         ICook coolCook = new CoolCook();  
  10.         if (cmd instanceof KaoyaCommand) {  
  11.             ((KaoyaCommand)cmd).setCook(hotCook);  
  12.         } else if (cmd instanceof TangCommand){  
  13.             ((TangCommand)cmd).setCook(hotCook);  
  14.         } else if (cmd instanceof PaihuangguaCommand) {  
  15.             ((PaihuangguaCommand)cmd).setCook(coolCook);  
  16.         }  
  17.         //加入到宏命令中  
  18.         menuCommand.addCommand(cmd);  
  19.     }  
  20.       
  21.     //点菜完毕  
  22.     public void orderDishFinish() {  
  23.         menuCommand.execute();  
  24.     }  
  25. }  
  1.  * 命令模式-队列请求 
  2.  * 还是点菜的例子 
  3.  * 核心思路就是把Command放到一个队列里面 
  1. //命令队列  
  2. class CommandQueue {  
  3.       
  4.     private static List<ICookCommandd> cmds = new ArrayList<ICookCommandd>();  
  5.     /*//书上还提到了记录日志到文件中,以便系统崩溃后能继续执行未做的菜 
  6.     static { 
  7.         cmds = ReadFromFile(fileName); 
  8.     } 
  9.     */  
  10.       
  11.     //在队列里面添加服务员传过来的菜。可能会有多个服务员同时传入菜单  
  12.     public synchronized static void addMenu(MenuCommandd menuCommand) {  
  13.         Collection<ICookCommandd> dishCmds = menuCommand.getCommands();  
  14.         for (ICookCommandd cmd : dishCmds) {  
  15.             cmds.add(cmd);  
  16.         }  
  17.         //WriteToFile(cmds);        //记录日志  
  18.     }  
  19.       
  20.     //取出一道菜准备交给厨师去做  
  21.     public synchronized static ICookCommandd getOneCommand() {  
  22.         ICookCommandd cmd = null;  
  23.         if (cmds.size() > 0) {  
  24.             cmd = cmds.get(0);  
  25.             cmds.remove(0);  
  26.             //WriteToFile(cmds);        //记录日志  
  27.         }  
  28.         return cmd;  
  29.     }  
  30.       
  31. }
http://sishuok.com/forum/blogPost/list/96.html
Example: Start computer

The Meta Command Pattern allows you to create macros of commands so that you can execute multiple commands at once.
for(command:commands) command.execute();

The Command Pattern means lots of command classes
When you use the Command Pattern, you end up with a lot of small classes—the concrete Command implementations—that each encapsulate the request to the corresponding receiver. 

We can use lambda expressions(anonymous class) to simply it.

queuing requests, serialize and deserialize command

The Command Pattern decouples an object making a request from the one that knows how to perform it.
A Command object is at the center of this decoupling and encapsulates a receiver with an action (or set of actions) .
An invoker makes a request of a Command object by calling its execute() method, which invokes those actions on the receiver.
Invokers can be parameterized with Commands, even dynamically at runtime.
Commands may support undo by implementing an undo method that restores the object to its previous state before the execute() method was last called.
Macro Commands are a simple extension of Command that allow multiple commands to be invoked. Likewise, Macro Commands can easily support undo().

In practice, it is not uncommon for “smart” Command objects to implement the request themselves rather than delegating to a receiver.

http://sishuok.com/forum/blogPost/list/101.html
3.7  命令模式的优缺点
  • 更松散的耦合
        命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象,完全不知道具体实现对象是谁,也不知道如何实现。
  • 更动态的控制
        命令模式把请求封装起来,可以动态对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
  • 能很自然的复合命令
        命令模式中的命令对象,能够很容易的组合成为复合命令,就是前面讲的宏命令,从而使系统操作更简单,功能更强大。
  • 更好的扩展性
        由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象里面,然后就可以使用这个命令对象,已有的实现完全不用变化。
http://www.javacodegeeks.com/2015/09/java-8-lambda-expression-for-design-patterns-command-design-pattern.html

commandpattern
Command pattern is easily extendable and new action methods can be added in receivers to create new Command implementations without changing the client code.
1Macro macro = new Macro();
2macro.record(() -> fileReceiver.openFile());
3macro.record(() -> fileReceiver.writeFile());
4macro.record(() -> fileReceiver.closeFile());
5macro.run();

     Macro macro = new Macro();
2macro.record(fileReceiver::openFile);
3macro.record(fileReceiver::writeFile);
4macro.record(fileReceiver::closeFile);
5macro.run();
http://www.cnblogs.com/java-my-life/archive/2012/06/01/2526972.html
  命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
  命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
  每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
  命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:
  (1)命令模式使新的命令很容易地被加入到系统里。
  (2)允许接收请求的一方决定是否要否决请求。
  (3)能较容易地设计一个命令队列。
  (4)可以容易地实现对请求的撤销和恢复。
  (5)在需要的情况下,可以较容易地将命令记入日志。
  下面以一个示意性的系统,说明命令模式的结构。
  命令模式涉及到五个角色,它们分别是:
  ●  客户端(Client)角色:创建一个具体命令(ConcreteCommand)对象并确定其接收者。
  ●  命令(Command)角色:声明了一个给所有具体命令类的抽象接口。
  ●  具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
  ●  请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
  ●  接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
public class ConcreteCommand implements Command {
    //持有相应的接收者对象
    private Receiver receiver = null;
    /**
     * 构造方法
     */
    public ConcreteCommand(Receiver receiver){
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        //通常会转调接收者对象的相应方法,让接收者来真正执行功能
        receiver.action();
    }
}
public class Invoker {
    /**
     * 持有命令对象
     */
    private Command command = null;
    /**
     * 构造方法
     */
    public Invoker(Command command){
        this.command = command;
    }
    /**
     * 行动方法
     */
    public void action(){
        
        command.execute();
    }
}
    public static void main(String[] args) {
        //创建接收者
        Receiver receiver = new Receiver();
        //创建命令对象,设定它的接收者
        Command command = new ConcreteCommand(receiver);
        //创建请求者,把命令对象设置进去
        Invoker invoker = new Invoker(command);
        //执行方法
        invoker.action();
    }

AudioPlayer系统

  小女孩茱丽(Julia)有一个盒式录音机,此录音机有播音(Play)、倒带(Rewind)和停止(Stop)功能,录音机的键盘便是请求者(Invoker)角色;茱丽(Julia)是客户端角色,而录音机便是接收者角色。Command类扮演抽象命令角色,而PlayCommand、StopCommand和RewindCommand便是具体命令类。茱丽(Julia)不需要知道播音(play)、倒带(rewind)和停止(stop)功能是怎么具体执行的,这些命令执行的细节全都由键盘(Keypad)具体实施。茱丽(Julia)只需要在键盘上按下相应的键便可以了。
  录音机是典型的命令模式。录音机按键把客户端与录音机的操作细节分割开来。
  接收者角色,由录音机类扮演
    
    public void play(){
        System.out.println("播放...");
    }
    
    public void rewind(){
        System.out.println("倒带...");
    }
    
    public void stop(){
        System.out.println("停止...");
    }
}

具体命令角色类

    private AudioPlayer myAudio;
    
    public PlayCommand(AudioPlayer audioPlayer){
        myAudio = audioPlayer;
    }
    /**
     * 执行方法
     */
    @Override
    public void execute() {
        myAudio.play();
    }

}
  请求者角色,由键盘类扮演
public class Keypad {
    private Command playCommand;
    private Command rewindCommand;
    private Command stopCommand;
    
    public void setPlayCommand(Command playCommand) {
        this.playCommand = playCommand;
    }
    public void setRewindCommand(Command rewindCommand) {
        this.rewindCommand = rewindCommand;
    }
    public void setStopCommand(Command stopCommand) {
        this.stopCommand = stopCommand;
    }
    /**
     * 执行播放方法
     */
    public void play(){
        playCommand.execute();
    }
    /**
     * 执行倒带方法
     */
    public void rewind(){
        rewindCommand.execute();
    }
    /**
     * 执行播放方法
     */
    public void stop(){
        stopCommand.execute();
    }
}
  客户端角色,由茱丽小女孩扮演
public class Julia {
    public static void main(String[]args){
        //创建接收者对象
        AudioPlayer audioPlayer = new AudioPlayer();
        //创建命令对象
        Command playCommand = new PlayCommand(audioPlayer);
        Command rewindCommand = new RewindCommand(audioPlayer);
        Command stopCommand = new StopCommand(audioPlayer);
        //创建请求者对象
        Keypad keypad = new Keypad();
        keypad.setPlayCommand(playCommand);
        keypad.setRewindCommand(rewindCommand);
        keypad.setStopCommand(stopCommand);
        //测试
        keypad.play();
        keypad.rewind();
        keypad.stop();
        keypad.play();
        keypad.stop();
    }
}

宏命令

  所谓宏命令简单点说就是包含多个命令的命令,是一个命令的组合。
  设想茱丽的录音机有一个记录功能,可以把一个一个的命令记录下来,再在任何需要的时候重新把这些记录下来的命令一次性执行,这就是所谓的宏命令集功能。因此,茱丽的录音机系统现在有四个键,分别为播音、倒带、停止和宏命令功能。此时系统的设计与前面的设计相比有所增强,主要体现在Julia类现在有了一个新方法,用以操作宏命令键。
  系统需要一个代表宏命令的接口,以定义出具体宏命令所需要的接口。
复制代码
public interface MacroCommand extends Command {
    /**
     * 宏命令聚集的管理方法
     * 可以添加一个成员命令
     */
    public void add(Command cmd);
    /**
     * 宏命令聚集的管理方法
     * 可以删除一个成员命令
     */
    public void remove(Command cmd);
}
复制代码
  具体的宏命令MacroAudioCommand类负责把个别的命令合成宏命令。
复制代码
public class MacroAudioCommand implements MacroCommand {
    
    private List<Command> commandList = new ArrayList<Command>();
    /**
     * 宏命令聚集管理方法
     */
    @Override
    public void add(Command cmd) {
        commandList.add(cmd);
    }
    /**
     * 宏命令聚集管理方法
     */
    @Override
    public void remove(Command cmd) {
        commandList.remove(cmd);
    }
    /**
     * 执行方法
     */
    @Override
    public void execute() {
        for(Command cmd : commandList){
            cmd.execute();
        }
    }
}

命令模式的优点

  ●  更松散的耦合
  命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
  ●  更动态的控制
  命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
  ●  很自然的复合命令
  命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大。
  ●  更好的扩展性
  由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
http://www.informit.com/articles/article.aspx?p=2216990
What if we decided that a new method is needed in the SwitchableElement interface? Java 8 allows for the addition of default methods. This fact ties the implementation code for that new method directly to the interface

Also refer to http://sourcemaking.com/design_patterns/command
Read full article from Command Design Pattern

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts