07 January 2013

Command Design Pattern

In part 1 we created a simple Command Design Pattern in Java. Here in the second part we will add undo and redo capabilities to our implementation. This implementation will use the flow illustrated below. Instead of passing the receiver to the invoker and from the invoker to the command the client will be passing the receiver to the command and the command to the invoker. This, in my opinion, is the better of the two options.

Command Pattern Drawing 2
Command Pattern drawing where the command target/receiver is passed into the command and then the command is passed into the command manager/invoker.

From this we should end up with a class diagram similar to the following.

Complex Command Pattern UML Class Diagram
Complex Command Pattern UML Class Diagram

In our invoker we will need to switch to LinkedLists and add two lists. The new lists will be used to track the undo and redo commands. This implementation is shown below.

CharacterCommandManager.java
package complexcommand;
import java.util.*;

public class CharacterCommandManager {
  private Deque<ICharacterCommand> commands = new LinkedList();
  private Deque<ICharacterCommand> undoCommands = new LinkedList();
  private Deque<ICharacterCommand> redoCommands = new LinkedList();
  
  public CharacterCommandManager() {
  }
  
  public void add(ICharacterCommand command) {
    if(commands != null) {
      commands.add(command);
    }    
  }
  
  public void execute() {
    ICharacterCommand command=null;    
    while(!commands.isEmpty()) {
      command = commands.removeFirst();
      if(command != null) {
        command.execute();
        undoCommands.add(command);
      }
    }
  }

  public void undo() {
    ICharacterCommand command=null;
    if(!undoCommands.isEmpty()) {
      command = undoCommands.removeLast();
      if(command != null) {
        command.undo();
        redoCommands.add(command);
      }
    }
  }

  public void redo() {
    ICharacterCommand command=null;
    if(!redoCommands.isEmpty()) {
      command = redoCommands.removeLast();
      if(command != null) {
        command.execute();      
      }
    }
  }
}

Our receiver remains the same.

Character.java
package complexcommand;

public class Character {
  private String name;
  private int strength;
  private int dexterity;
  private int constitution;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getStrength() {
    return strength;
  }

  public void setStrength(int strength) {
    this.strength = strength;
  }


  public int getDexterity() {
    return dexterity;
  }

  public void setDexterity(int dexterity) {
    this.dexterity = dexterity;
  }

  public int getConstitution() {
    return constitution;
  }

  public void setConstitution(int constitution) {
    this.constitution = constitution;
  }

  
  public String toString() {
    return "name=" + name +
           "; strength=" + strength +
           "; dexterity=" + dexterity +
           "; constitution=" + constitution;
                   
  }
  
}

We have added the undo method to our ICharacterCommand interface.

ICharacterCommand.java
package complexcommand;

public interface ICharacterCommand {
  public void execute();
  public void undo();
}

Our command classes which implement the interface have added an “undo” attribute.

NameCommand.java
package complexcommand;

public class NameCommand implements ICharacterCommand {
  private String name;
  private String undoName;
  private Character character;
  
  public NameCommand(Character character, String name) {
    this.name=name;
    this.character=character;
  }
  
  public void execute() {
    if(character != null) {
      this.undoName = character.getName();
      character.setName(name);
    }
  }
  
  public void undo() {
    if(character != null) {
      character.setName(undoName);
    }
  }  
  
  public String toString() {
    return name;
  }
  
}
StrengthCommand.java
package complexcommand;

public class StrengthCommand implements ICharacterCommand {
  private int strength;
  private int undoStrength;
  private Character character;
  
  public StrengthCommand(Character character, int strength) {
    this.strength=strength;
    this.character=character;
  }
  
  public void execute() {
    if(character != null) {
      this.undoStrength=character.getStrength();
      character.setStrength(strength);
    }
  }

  public void undo() {
    if(character != null) {
      character.setStrength(undoStrength);
    }
  }
  
}
DexterityCommand.java
package complexcommand;

public class DexterityCommand implements ICharacterCommand {
  private int dexterity;
  private int undoDexterity;
  private Character character;
  
  public DexterityCommand(Character character, int dexterity) {
    this.dexterity=dexterity;
    this.character=character;
  }
  
  public void execute() {
    if(character != null) {
      this.undoDexterity=character.getDexterity();
      character.setDexterity(dexterity);
    }
  }

  public void undo() {
    if(character != null) {
      character.setDexterity(undoDexterity);
    }
  }
  
}
ConstitutionCommand.java
package complexcommand;

public class ConstitutionCommand implements ICharacterCommand {
  private int constitution;
  private int undoConstitution;
  private Character character;
  
  public ConstitutionCommand(Character character, int constitution) {
    this.constitution=constitution;
    this.character=character;    
  }
  
  public void execute() {
    if(character != null) {
      this.undoConstitution=character.getConstitution();
      character.setConstitution(constitution);
    }
  }

  public void undo() {
    if(character != null) {
      character.setConstitution(undoConstitution);
    }
  }
  
}

Our client now sets the receivers attributes twice and calls the undo and redo methods. Which nicely demonstrates the functionality that we have added.

ComplexCommand.java
package complexcommand;

public class ComplexCommand {

  public static void main(String[] args) {
    CharacterCommandManager manager = new CharacterCommandManager();
    Character character = new Character();
    
    ICharacterCommand command = new NameCommand(character,"Bob");
    manager.add(command);

    command = new StrengthCommand(character,14);
    manager.add(command);

    command = new DexterityCommand(character,13);
    manager.add(command);

    command = new ConstitutionCommand(character,11);
    manager.add(command);
    
    manager.execute();
    System.out.println("First time attributes set:");
    System.out.println(character + "n");
    
    command = new NameCommand(character,"Joe");
    manager.add(command);

    command = new StrengthCommand(character,18);
    manager.add(command);

    command = new DexterityCommand(character,10);
    manager.add(command);

    command = new ConstitutionCommand(character,12);
    manager.add(command);

    manager.execute();
    System.out.println("Second time attributes set:");
    System.out.println(character + "n");
    
    manager.undo();
    manager.undo();
    manager.undo();
    manager.undo();
    System.out.println("After undo operation called 4 times:");
    System.out.println(character + "n");
    
    manager.redo();
    manager.redo();
    manager.redo();
    manager.redo();
    System.out.println("After redo operation called 4 times:");
    System.out.println(character + "n");
    
  }
}

If all has gone well we will see the following results.

SimpleCommand Results
First time attributes set:
name=Bob; strength=14; dexterity=13; constitution=11

Second time attributes set:
name=Joe; strength=18; dexterity=10; constitution=12

After undo operation called 4 times:
name=Bob; strength=14; dexterity=13; constitution=11

After redo operation called 4 times:
name=Joe; strength=18; dexterity=10; constitution=12

Less Is More ~ Older posts are available in the archive.