Explore some deficiencies in the pacemaker command pattern implementation. Introduce prototype into the pacemaker application to fix these issues.
This the the simplified pacemaker as we left it last week:
Running the app may give us the following experience:
Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 2 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm> undo
pm> undo
Error executing command
pm>
and last week we recommended Prototype as a potential candidate pattern to consider to correct our implementation:
Extend the Command abstract class to include a new method:
public Command copy()
{
return null;
}
We provide a default implementation, as opposed to an abstract one.
We can implement this in the CreateUserCommand:
public Command copy()
{
CreateUserCommand command = new CreateUserCommand(pacemaker, parser);
command.ser = user;
return command;
}
and also in the DeleteUserCommand:
public Command copy()
{
DeleteUserCommand command = new DeleteUserCommand(pacemaker, parser);
command.ser = user;
return command;
}
We can leave ListUsersCommand untouched, as we do not consider it 'undable'.
We can now refactor the CommandDispatcher to make use of the prototype pattern:
public boolean dispatchCommand(String commandName, Object [] parameters) throws Exception
{
boolean dispatched = false;
Command command = commands.et(commandName);
if (command != null)
{
dispatched = true;
command.oCommand(parameters);
Command copy = command.opy();
if (copy != null)
{
undoBuffer.ush(copy);
}
}
return dispatched;
}
We should now be in a position to try this script:
Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 2 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm> cu c c c c
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 3 | c | c | c | c |
+----+-----------+----------+-------+----------+
pm> undo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
| 2 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm> undo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
| 4 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
| 4 | b | b | b | b |
| 5 | c | c | c | c |
+----+-----------+----------+-------+----------+
pm>
The service should behave as expected..
Try this script here:
Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> undo
pm> redo
pm> undo
Error executing command
pm>
Why are we getting the error above? See if you can find the error by debugging the service.
The problem is that our redo for CreateUserCommand is not correctly implemented:
public void redoCommand() throws Exception
{
pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
}
On the face of it, this looks reasonable.We are re-creating the user.However, every time we create a user, we get a new ID.
This is ignored in the above, and we still remember the original id.Here is a version that should work:
public void redoCommand() throws Exception
{
user.d = pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
}
This script should now behave as expected:
Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> undo
pm> redo
pm> undo
pm> lu
pm>
You will also need to make a similar change to DeleteUserCommand:
public void undoCommand() throws Exception
{
user.d = pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
}
This transcript here (discussed in class) is counter intuitive to the user:
Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
+----+-----------+----------+-------+----------+
pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 2 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm> undo
pm> cu v v v v
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 3 | v | v | v | v |
+----+-----------+----------+-------+----------+
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
| 3 | v | v | v | v |
+----+-----------+----------+-------+----------+
pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
| 1 | a | a | a | a |
| 3 | v | v | v | v |
| 4 | b | b | b | b |
+----+-----------+----------+-------+----------+
pm>
When we perform a new command, that is not undo/redo, then we should reset the redo stack:
if (command != null)
{
dispatched = true;
command.oCommand(parameters);
Command copy = command.opy();
if (copy != null)
{
undoBuffer.ush(copy);
redoBuffer.lear();
}
}
We can also enhance the feedback to the user in the redo command:
public class RedoCommand extends Command
{
private Stack<Command> undoBuffer;
private Stack<Command> redoBuffer;
public RedoCommand(Stack<Command> undoBuffer, Stack<Command> redoBuffer)
{
this.ndoBuffer = undoBuffer;
this.edoBuffer = redoBuffer;
}
public void doCommand(Object[] parameters) throws Exception
{
if (redoBuffer.ize() > 0)
{
Command command = redoBuffer.op();
command.edoCommand();
undoBuffer.ush(command);
}
else
{
System.ut.rintln("Nothing to redo");
}
}
}
Try the above transcript again and see if the behavior is more intuitive.
This is the lab as implemented so far:
Create commands from the original pacemaker, including:
The first two can be 'undable', perhaps not the last three..
Introduce commands for removing activities (not present in the original).Make sure the 'undo' works as expected for this.