Java Tip 68: Learn how to implement the Command pattern in Java

Add flexibility and extensibility to your programs with this object-oriented equivalent of the callback

1 2 Page 2
Page 2 of 2
//TestTransactionCommand.java
import java.util.*;
final class CommandReceiver {
  private int[] c;
  private CommandArgument a;
     private CommandReceiver(){
       c = new int[2];
     }
     private static CommandReceiver cr = new CommandReceiver();
     public static CommandReceiver getHandle() {
     return cr;
     }
     public void setCommandArgument(CommandArgument a) {
     this.a = a;
     }
     public void methAdd() {
     c = a.getArguments();
         System.out.println("The result is " + (c[0]+c[1]));
     }
     public void methSubtract() {
     c = a.getArguments();
         System.out.println("The result is " + (c[0]-c[1]));
     }
}
 class CommandManager {
   private Command myCommand;
   public CommandManager(Command  myCommand) {
     this.myCommand  =  myCommand ;    
   }
   public void runCommands( ) {
            myCommand.execute();     
   }
 }
class TransactionCommand implements Command {
  private CommandReceiver commandreceiver;
  private Vector commandnamelist,commandargumentlist; 
  private String commandname;
  private CommandArgument commandargument;
  private Command command;
  public TransactionCommand () {
    this(null,null);
  }
  public TransactionCommand ( Vector  commandnamelist, Vector
commandargumentlist){
    this.commandnamelist = commandnamelist;
    this.commandargumentlist = commandargumentlist;
    commandreceiver =  CommandReceiver.getHandle();  
  }
  public void execute( ) {
    for (int i = 0; i < commandnamelist.size(); i++) {
      commandname = (String)(commandnamelist.get(i));
      commandargument = (CommandArgument)((commandargumentlist.get(i)));
      commandreceiver.setCommandArgument(commandargument);
      String classname = commandname + "Command";
         try {
           Class cls = Class.forName(classname);
           command = (Command) cls.newInstance();
         }
         catch (Throwable e) {   
                  System.err.println(e);
         }
      command.execute();
    } 
  }
}
 class AddCommand extends TransactionCommand {
   private CommandReceiver cr;
   public AddCommand () {
      cr = CommandReceiver.getHandle();  
   }  
   public void execute( ) {  
     cr.methAdd();  
   }   
 }
 class SubtractCommand extends TransactionCommand {
   private CommandReceiver cr;
   public SubtractCommand () {
      cr = CommandReceiver.getHandle();  
   }
   public void execute( ) {
     cr.methSubtract();
   }   
 }
 class CommandArgument {
   private int[] args;
   CommandArgument() {
     args = new int[2];
   }
   public int[] getArguments() {
    return args;
   }
   public void setArgument(int i1, int i2) {
         args[0] = i1; args[1] = i2;
   }
 }
 public class TestTransactionCommand {
   private  Vector clist,alist; 
   public TestTransactionCommand() {
     clist = new Vector(); 
       alist = new Vector();
   }
   public void clearBuffer(Vector c, Vector a) {
     clist.removeAll(c);
       alist.removeAll(a); 
   }
   public Vector getClist() {
     return clist;
   }
   public Vector getAlist() {
     return alist;
   }
    public static void main(String[] args) {
       CommandArgument ca,ca2;
     TestTransactionCommand t = new TestTransactionCommand();
     ca = new CommandArgument();
     ca.setArgument(2,8);
     Vector myclist = t.getClist();
     Vector myalist = t.getAlist();
     myclist.addElement("Add"); myalist.addElement(ca);
     TransactionCommand tc = new TransactionCommand(myclist,myalist);
     CommandManager cm = new CommandManager(tc);       
                    cm.runCommands();
     t.clearBuffer(myclist,myalist);
     ca2 = new CommandArgument();
     ca2.setArgument(5,7);
     myclist = t.getClist();
     myalist = t.getAlist();
     myclist.addElement("Subtract"); myalist.addElement(ca2);
     myclist.addElement("Add"); myalist.addElement(ca2);
     TransactionCommand tc2 = new TransactionCommand(myclist,myalist);        
     CommandManager cm2 = new CommandManager(tc2);       
                    cm2.runCommands();
   }
 }               

The command and its arguments are stored in a list and encapsulated into the generic TransactionCommand object. The generic TransactionCommand is registered with the CommandManager. The commands can be run at any time by invoking the runCommands() interface in the CommandManager class.

The client code doesn't depend on any of the concrete TransactionCommand subclasses, which means I have programmed to the interface and not the implementation. This gives us extensibility: to add a new command, we need to define a new TransactionCommand subclass and provide the implementation for the new command-processing method in the CommandReceiver class. That's all there is to it.

Conclusion

The Command pattern has the following advantages:

  1. A command decouples the object that invokes the operation from the one that knows how to perform it.

  2. A command is a first-class object. It can be manipulated and extended like any other object.

  3. Commands can be assembled into a composite command.

  4. It's easy to add new commands, because you don't have to change the existing classes.

When the sequence of commands executed is saved in a history list, you can iterate through the list to support undo and redo operations. You must have an unexecute() operation in the Command interface to implement this functionality. I will leave this as an exercise for the reader.

Some of this material was inspired by class notes from an object-oriented design class given by William E. Fairfield at the University of California Santa Cruz Extension.

Bala Paranj is a corporate applications engineer at Mentor Graphics, Microtec Division. There he resolves complex technical problems of real-time operating systems, including test case generation, and research and evaluation of potential solutions. He has a Master of Science degree in Electrical Engineering from The Wichita State University, Wichita, Kansas. His research interests include applying design patterns to solve networking problems, concurrent programming, and implementation of the solution in Java. When he's not programming, he enjoys dancing, volleyball, chess, and spending time in nature.

Learn more about this topic

1 2 Page 2
Page 2 of 2