Version 3.0 of our application framework enhances version 2.0 with a command processor and a command base class. The new framework will now be able to handle "undo" and "redo" commands. Instead of depending on a derived class to implement a message handler, the new active controller depends on a derived class to implement a virtual factory method for creating commands.
Each command is equipped with a pointer to the model. The active controller passes the commands it creates to the command processor, which maintains two stacks of command pointers: the undo stack, and the redo stack.
All application-dependent commands are derived from AFW's command base class, which contains a pointer to the model, a pure virtual execute() function, and a virtual undo() function that must be redefined in derived classes that represent undoable commands:
class Command
{
public:
Command(const string& nm =
"?", Model* m = 0)
{
theModel = m;
name = nm;
undoable = true;
}
virtual ~Command() {}
string getName() { return name; }
bool getUndoable() { return undoable; }
virtual void execute() = 0;
virtual void undo() {}; // override if
undoable
protected:
Model* theModel;
string name;
bool undoable; // can clear in derived
classes
};
Also notice that each command encapsulates a flag indicating if it is undoable (commands like "print" and "quit" are not undoable) and a name. The name will be used to remind users which command they are undoing or redoing.
Command processors are singletons (see Chapter 3). A command processor has two command stacks. One holds pointers to commands that can be undone, the other to commands that can be redone:
class CommandProcessor
{
public:
static CommandProcessor*
makeCommandProcessor()
{
if (!theCommandProcessor)
theCommandProcessor = new
CommandProcessor();
return theCommandProcessor;
}
void execute(Command* cmmd);
Result undo();
Result redo();
private:
static CommandProcessor* theCommandProcessor;
CommandProcessor() {}
CommandProcessor(const
CommandProcessor& cp) {}
~CommandProcessor() {}
stack<Command*> undoStack,
redoStack;
};
Don't forget to define the static pointer to the one and only command processor:
CommandProcessor* CommandProcessor::theCommandProcessor = 0;
If commands are smart, then the command processor's execute() function is simple. It asks its command argument to execute itself, then, if the command is undoable, it pushes it onto the undo stack:
void CommandProcessor::execute(Command* cmmd)
{
cmmd->execute();
if (cmmd->getUndoable())
undoStack.push(cmmd);
}
The command processor's undo() function removes the top command from the undo stack (commands must be undone in the reverse order in which they were executed), calls its undo() function, then pushes it onto the redo stack:
Result CommandProcessor::undo()
{
if (undoStack.empty())
throw AFWError("Nothing left to
undo.");
Command* cmmd = undoStack.top();
undoStack.pop();
cmmd->undo();
redoStack.push(cmmd);
return cmmd->getName();
}
The command processor's redo() function is almost the mirror image of the undo() function, except redoing a command simply means calling its execute() function again:
string CommandProcessor::redo()
{
if (redoStack.empty())
throw AFWError("Nothing left to
redo.");
Command* cmmd = redoStack.top();
redoStack.pop();
cmmd->execute();
undoStack.push(cmmd);
return cmmd->getName();
}
The active controller in AFW 3.0 is constructed from the AFW 2.0 active controller by adding a pointer to the command processor, a virtual factory method for creating commands, and an implementation of the pure virtual handle() function inherited from the Controller base class:
class ActiveController: public Controller
{
public:
ActiveController(Model* m = 0);
void controlLoop();
protected:
virtual Command* makeCommand(Message
msg) = 0;
CommandProcessor* theCommandProcessor;
Result handle(Message msg);
// etc.
};
The controller's constructor creates the view handler and command processor singletons:
ActiveController::ActiveController(Model* m)
: Controller(m)
{
theViewHandler =
ViewHandler::makeViewHandler();
if (theModel)
theModel->setViewHandler(theViewHandler);
theCommandProcessor =
CommandProcessor::makeCommandProcessor();
}
The handle() function no longer needs to be implemented in framework customizations (the virtual factory method will need to be implemented instead). The handle() function uses the virtual factory method to convert the message parameter into a command object, which is then passed to the command processor's execute() function:
Result ActiveController::handle(Message msg)
{
Command *cmmd = makeCommand(msg);
theCommandProcessor->execute(cmmd);
return "done";
}
We can now add clauses for handling "undo" and "redo" commands to the active controller's control loop:
else if (msg == "undo")
{
res = theCommandProcessor->undo();
cout << res << "
undone\n";
}
else if (msg == "redo")
{
res = theCommandProcessor->redo();
cout << res << "
redone\n";
}
// etc.
Version 3.0 of Brick CAD, our CAD/CAM system for designing bricks, customizes AFW 3.0 instead of AFW 2.0. Fortunately, version 3.0 can re-use the version 2.0 Model and View derived classes:
class Brick: public Model { /* as before */ };
class FrontView: public View { /* as before */ };
class SideView: public View { /* as before */ };
class TopView: public View { /* as before */ };
We will also need to derive a class from the ActiveController class (this will be different from the 2.0 version). We will also need to create command-derived classes for each application-dependent command:
Initially, Brick CAD 3.0 edits a brick with default dimensions:
-> show
height = 5 inches
width = 5 inches
length = 5 inches
volume = 125 inches^3
weight = 5 pounds
done
We create top and side views, which are notified when we subsequently alter the brick's length and height:
-> setLength 20
*** TOP VIEW ***
width = 5 inches
length = 20 inches
*** SIDE VIEW ***
height = 5 inches
length = 20 inches
done
-> setHeight 15
*** TOP VIEW ***
width = 5 inches
length = 20 inches
*** SIDE VIEW ***
height = 15 inches
length = 20 inches
done
Let's display the model's properties once again:
-> show
height = 15 inches
width = 5 inches
length = 20 inches
volume = 1500 inches^3
weight = 60 pounds
done
Notice that the "undo" command undoes the "setHeight" command, not the previous, undoable "show" command:
-> undo
*** TOP VIEW ***
width = 5 inches
length = 20 inches
*** SIDE VIEW ***
height = 5 inches
length = 20 inches
setHeight undone
Typing "undo" a second time undoes the original "setLength" command:
-> undo
*** TOP VIEW ***
width = 5 inches
length = 5 inches
*** SIDE VIEW ***
height = 5 inches
length = 5 inches
setLength undone
But typing "undo" a third time produces an error message:
-> undo
Error: Nothing left to undo.
Next, we "redo" the previously undone "setLength" command:
-> redo
*** TOP VIEW ***
width = 5 inches
length = 20 inches
*** SIDE VIEW ***
height = 5 inches
length = 20 inches
setLength redone
-> redo
*** TOP VIEW ***
width = 5 inches
length = 20 inches
*** SIDE VIEW ***
height = 15 inches
length = 20 inches
setHeight redone
-> redo
Error: Nothing left to redo.
The controller will convert the "setHeight AMT" message into an instance of the following command-derived class:
class SetHeightCommand: public Command
{
public:
SetHeightCommand(Brick* b = 0, int amt
= 5)
: Command("setHeight", b)
{
if (amt <= 0) throw
AFWError("amount must be positive");
newHeight = amt;
}
void execute();
void undo();
private:
int newHeight, oldHeight;
};
The Command Processor pattern decouples command creation time from command execution time, therefore "setHeight" commands need to retain the new height as a member variable. When a "setHeight" command is executed, the old height is recorded in a second member variable before the height of the model is modified:
void SetHeightCommand::execute()
{
Brick* b = (Brick*) theModel;
oldHeight = b->getHeight();
b->setHeight(newHeight);
}
The undo() function simply changes the height of the brick back to the retained old height:
void SetHeightCommand::undo()
{
Brick* b = (Brick*) theModel;
b->setHeight(oldHeight);
}
The "setLength" and "setWidth" commands follow the same pattern. By contrast, the controller converts "show" messages into instances of the ShowCommand class:
class ShowCommand: public Command
{
public:
ShowCommand(Brick* b = 0)
: Command("show", b)
{
undoable = false;
}
void execute();
};
Notice that the constructor clears the protected undoable flag inherited from the command base class. This is because "show" commands can't be undone. Of course we don't need to implement an undo() function in this case, but we still need to provide an appropriate execute() function.
The Brick CAD controller must provide an implementation of the pure virtual factory method inherited from the AFW 3.0 ActiveController base class:
class BCController: public ActiveController
{
public:
BCController(Model* m = 0):
ActiveController(m) {}
Command* makeCommand(Message msg);
};
The factory method implementation closely parallels the BCController::handle() function defined in Brick CAD 2.0. The type of the input message is determined, but instead of performing the prescribed action, a corresponding command object is created and returned to the caller:
Command* BCController::makeCommand(Message msg)
{
Brick* b = (Brick*)theModel;
Command* cmmd = 0;
double arg = 0;
if (msg == "setHeight")
{
cin >> arg;
if (!cin) throw
AFWError("amount must be a number");
cmmd = new SetHeightCommand(b, arg);
}
else if (msg == "setWidth") {
... }
else if (msg == "setLength")
{ ... }
else if (msg == "show") cmmd
= new ShowCommand(b);
else throw
AFWError(string("unrecognized command: ") + msg);
return cmmd;
}