Memento Design Pattern in Delphi
This session consists of the development of a small application to read and pretty-print XML and CSV files. Along the way, we explain and demonstrate the use of the following patterns: State, Interpreter, Visitor, Strategy, Command, Memento, and Facade.
Without violating encapsulation, capture and externalise an object's internal state so that the object can be restored to this state later.
We'll use the Memento pattern to help use store state so that we can implement undo for some of our commands. It's a very simple pattern, the only trick being that to return an object to an earlier state often requires us to set values of private fields, and we don't want to expose them as public because we don't want other objects to have access to those fields.
To do this, we normally need three types of objects. One is obviously the memento itself. This will store the state information that we will need later. The next is a caretaker, which will store the memento (or possibly several), until such time as it is needed to restore an earlier state. This may be never, of course. The last class is the originator, which will both create mementos, and use them to go back to an earlier state.
There is a difficulty here in Delphi. The memento classes normally have to have a wide interface to the originators, so that they can set all the necessary values. However, this interface should not be available to other classes, otherwise we are defeating the purpose of encapsulating this state information in the first place. So we have two choices. One is to have a constructor with many parameters (or possibly an object, if we were to use the Introduce Parameter Object refactoring). The other, and in my view preferable, option is to define the memento in the same unit as the originator class (in C++ we could make it a friend class).
In our example, the originator is the document, and Document.pas has the definition of the memento we will be using:
TDocumentMemento = class(TObject)
private
FText : string;
end;
As you can see, this is ultra-simple. A less-contrived example would normally be more complex. We can get and set the state using the document's Memento property that we saw in the earlier definition. The accessor methods for it are defined as:
function TDocument.GetMemento : TDocumentMemento;
begin
// Create a new memento, store the current document state, and return it
Result := TDocumentMemento.Create;
Result.FText := Text;
end;
procedure TDocument.SetMemento(const Value : TDocumentMemento);
begin
// Update the document state from the memento. Normally this would be more complex
Text := Value.FText;
end;
You can see that they are effectively the same as those for the Text
property, but this is not normally the case, and the state would usually require us to save several field values.
Let's have a look at the implementation of the pretty printing command:
procedure TPrettyPrintCommand.DoExecute;
begin
// Just in case, make sure the current memento is freed
FreeAndNil(FOriginalDoc);
FOriginalDoc := FDocument.Memento;
FDocument.PrettyPrint;
end;
procedure TPrettyPrintCommand.DoRollback;
begin
if Assigned(FOriginalDoc) then begin
FDocument.Memento := FOriginalDoc;
end;
end;
You can see that we store the current state of the document in the FOriginalDoc
field in the command, which is playing the role of caretaker, before applying the pretty printing changes. Rolling back the changes just means we get the document to set its state back to that saved in the memento.
Now that we have all that working, we can implement the command undo/redo list.