Facade 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.
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Normally, to implement the Facade pattern, you need to define a facade class, which acts as the sole interface of some subsystem to other classes. Usually this is because the subsystem is quite complex, often as a result of refactoring and applying design patterns. These practices often result in more, smaller classes, which can be difficult for clients to use. Often they are tightly coupled as well. Using a facade hides that.
You can also use a facade to shift dependencies between elements of the subsystem and the clients that use them to being between clients and the facade, reducing the overall coupling of the application. Changes can be made to the subsystem without affecting the clients.
In either case, subsystem objects should not keep a reference to the facade, but should only perform tasks assigned to them.
Our facade is defined in CommandFacade.pas as follows:
TCommandFacade = class(TObject) private FCommandList : TObjectList; FCurrCommand : Integer; // Possibly this should be registered, rather than created here FDocument : TDocument; FMemo : TMemo; procedure ClearOldCommands; procedure PerformCommand(CommandClass : TDocumentCommandClass; ClearCommandList : Boolean); procedure UpdateMemo; protected public constructor Create; destructor Destroy; override; procedure OpenDocument; procedure CloseDocument; procedure Undo; procedure Redo; procedure SearchAndReplace; procedure PrettyPrint; // This allows a memo control to be updated when the document content // changes. This should really be an Observer etc, but the session is // only so long! procedure RegisterMemo(AMemo : TMemo); end;
Our facade object will keep a list of the commands that have been executed in the
FCommandList field, and keep track of the last executed one in
FCurrCommand. We will also store the document object for the application here, so that we can pass it to the commands as necessary. We also have a list of methods, each representing one of the available commands. Notice that we have two extra – undo and redo – that we will examine in more detail in a minute.
The last thing we have is a reference to the memo control that will display the document. We attach this by using the
RegisterMemo method. Ideally, we would implement the Observer or Mediator pattern to track changes, but this paper is long enough already.
If we look at the code for the pretty printing command method, we can see how the four main actions are implemented:
procedure TCommandFacade.PrettyPrint; begin PerformCommand(TPrettyPrintCommand,False); UpdateMemo; end;
PerformCommand routine does this:
procedure TCommandFacade.PerformCommand(CommandClass : TDocumentCommandClass; ClearCommandList : Boolean); var NewCommand : TDocumentCommand; begin NewCommand := CommandClass.Create(FDocument); try NewCommand.Execute; if ClearCommandList then begin FCommandList.Clear; end else begin // If have done an undo and then choose a new command, clear the // old following commands ClearOldCommands; FCurrCommand := FCommandList.Add(NewCommand); end; except // Only add command to the command list if doesn't raise an exception NewCommand.Free; end; end;
We create a new command object, with its concrete class being determined by the type of command to execute. Opening a file or closing a file will clear the undo/redo list, pretty printing or doing a search-and-replace will add the command to the end of the list. If we have rolled back through the list, all the commands we have undone will be removed from the list before adding the new one. After performing the command action, the memo is updated to show the new document text (which might be blank if we closed the file).
If you look at the source code that implements undo and redo, you will find that they move through the undo/redo list executing the relevant command. ).
We have a very simple implementation here, and you should refer to the GoF book for ways in which an undo/redo list might be modified under other circumstances. It's an example that stretches over several patterns, as we have discovered, but there are still others that can apply, like Flyweight, for instance.
The one last pattern that we will apply is Singleton. We will make sure our facade object can only have one instance created at a time. We'll use the simple method of counting the instances, but you will find there are more complex ways to do this. I've always found them to be overkill, but your mileage may vary. Our global Commands variable is created and destroyed in the initialization and finalization sections of the CommandFacade.pas unit.
To see how much difference using Facade makes, you should look at the user interface code in MainForm.pas, where you will see that the code for each menu item event handler has one line of code. What would have been quite complex, managing documents, command lists, parsers, interpreters, and so on is now completely hidden from the UI. If we wanted to change the interface it would be a doddle. Adding new commands is also pretty easy.