More Design Patterns
Source code for this paper.
This paper is intended to introduce some of the lesser known design patterns from the GoF (Gang of Four) Design Patterns book. It will be assumed that readers have some knowledge of patterns. You will definitely need to understand object oriented techniques.
We’ll see how to go about using patterns in the development of an example program, albeit a somewhat contrived one. We will go through the process of designing the system, deciding on the patterns to use, and their implementations in Delphi.
Note that a pattern cannot be expressed in code. The examples here are merely some ways of implementing patterns, and it is possible, and indeed desirable, to come up with completely different implementations in other circumstances. Some of those options will be discussed as we go along.
Remember: A pattern is not a code template, so don’t use these examples that way, ok?
The example we will develop is of a small piece of software that can read and display XML and comma separated value (CSV) files. The program will automatically detect which type of file is being read, and parse and display the file contents appropriately.
We will go through the design process, deciding which patterns to use. The finished code will (hopefully!) look very clean, but it certainly didn’t start life that way. Some of the refactorings used to clean it up are used as examples in the Refactorings paper.
The first thing we will decide is how to read CSV files. Just so we all know what we’re talking about, a CSV file is a text file containing lines like this:Stuff,123,”More stuff, but this can contain commas”,,LastField
Note that double quotes are used to wrap strings that contain commas, and that fields can be empty.
One of the traditional ways to parse strings like this is to use a state machine. Julian Bucknall’s excellent Tomes of Delphi: Algorithms and Data Structures> contains just such a thing. Note that his particular example does not deal with carriage returns or line feeds, it assumes that the lines have been split up already and are fed to the routine one at a time to extract the fields. The Refactoring paper tells the exciting story of how this little procedure grew up into a fully-fledged pattern implementation. We’re just going to look at the finished product here.
The State pattern is used when an object’s behaviour changes at run-time depending on its state. Indicators of the potential for using the pattern are long case statements or lists of conditional statements (the Switch Statements "bad smell", to use refactoring parlance). In Delphi (as in most languages) a given object cannot actually change its class, so we have to use other schemes to mimic that behaviour, as we shall see.
The participants in an implementation are the context and the states. The context is the interface presented to clients of the subsystem being modelled by the State pattern. In our case this will be the TCsvParser class. Clients will never see the states, allowing us to change them at will. The only interface client subsystems are interested in is extracting the fields from a line of text.
We do this by using a finite state machine (FSM). Essentially, an FSM is a model of a set of states. From each state, particular inputs can cause transitions to other states. There are two sorts of special states. The Start state is the state the FSM is in before beginning work. End states are those where the processing finishes, and are usually denoted by double circles. The FSM for the parser is shown below:
In the State pattern, each of the states becomes a subclass of the base state class. Each subclass must implement the abstract method ProcessChar which handles the input character and decides on the next state.
unit CsvParser; interface uses Classes; type TCsvParser = class; // Forward declaration TParserStateClass = class of TCsvParserState; TCsvParserState = class(TObject) private FParser : TCsvParser; procedure ChangeState(NewState : TParserStateClass); procedure AddCharToCurrField(Ch : Char); procedure AddCurrFieldToList; public constructor Create(AParser : TCsvParser); procedure ProcessChar(Ch : AnsiChar;Pos : Integer); virtual; abstract; end; TCsvParserFieldStartState = class(TCsvParserState) private public procedure ProcessChar(Ch : AnsiChar;Pos : Integer); override; end; TCsvParserScanFieldState = class(TCsvParserState) private public procedure ProcessChar(Ch : AnsiChar;Pos : Integer); override; end; TCsvParserScanQuotedState = class(TCsvParserState) private public procedure ProcessChar(Ch : AnsiChar;Pos : Integer); override; end; TCsvParserEndQuotedState = class(TCsvParserState) private public procedure ProcessChar(Ch : AnsiChar;Pos : Integer); override; end; TCsvParserGotErrorState = class(TCsvParserState) private public procedure ProcessChar(Ch : AnsiChar;Pos : Integer); override; end; TCsvParser = class(TObject) private FState : TCsvParserState; // Cache state objects for greater performance FFieldStartState : TCsvParserFieldStartState; FScanFieldState : TCsvParserScanFieldState; FScanQuotedState : TCsvParserScanQuotedState; FEndQuotedState : TCsvParserEndQuotedState; FGotErrorState : TCsvParserGotErrorState; // Fields used during parsing FCurrField : string; FFieldList : TStrings; function GetState : TParserStateClass; procedure SetState(const Value : TParserStateClass); protected procedure AddCharToCurrField(Ch : Char); procedure AddCurrFieldToList; property State : TParserStateClass read GetState write SetState; public constructor Create; destructor Destroy; override; procedure ExtractFields(const s : string;AFieldList : TStrings); published end;
If we examine the parser class first, we see that we have a private instance of each of the state subclasses. In our case, where we could be parsing very long files, and the state is changing frequently, it makes sense to create all the objects once, and keep track of the current state. If you have a situation where you have very many states (which is when this pattern really starts making a difference), especially if they are only needed occasionally, then it makes more sense to create and free the states on the fly. This might be an opportunity to use the automatic garbage collection property of interfaces, but be careful not to mix class and interface access to the state objects. It might also be a time to consider the Flyweight pattern (I’m going to refer you to the GoF for that).
Note that we are keeping track of the state using the class of the current state object. We can use a protected property (an example of the Self Encapsulate Field refactoring, as it happens) to access the field. The parser class also keeps the current field and the list of extracted fields. The states will use the protected methods to update them.
The states can manage this because the parser is passed as a parameter in the constructor. It is quite common for state objects to need access to the context in which they are being used. The base abstract state class defines methods for changing state, and updating the parser. Descendant classes only need to implement the character processing routine.
Let’s have a look at one of these routines, for the start state.
procedure TCsvParserFieldStartState.ProcessChar(Ch : AnsiChar;Pos : Integer); begin case Ch of '"' : ChangeState(TCsvParserScanQuotedState); ',' : AddCurrFieldToList; else AddCharToCurrField(Ch); ChangeState(TCsvParserScanFieldState); end; end;
If we get a double quote, then the FSM goes into the Scan Quoted state, a comma means we have come to the end of the field, so we should add it to the list, and anything else means we are starting a new field.
However, in the Scan Quoted state shown below, the transition when we get a double quote is different. This is what we mean by the behaviour depending on the state.
procedure TCsvParserScanQuotedState.ProcessChar(Ch : AnsiChar;Pos : Integer); begin if (Ch = '"') then begin ChangeState(TCsvParserEndQuotedState); end else begin AddCharToCurrField(Ch); end; end;
The rest of the code is quite straightforward. The only slightly different state is the Error state, where we raise an exception. The parser has one long method, only because it has to handle validity checks, setting up, and so on. The essential lines of ExtractFields are:
// Read through all the characters in the string for i := 1 to Length(s) do begin // Get the next character Ch := s[i]; FState.ProcessChar(Ch,i); end;
This reads through the input line s, sending each character to the current state. Some sort of processing loop like this is not uncommon. I’ll leave the rest of the code to go through at your leisure. It’s all in CsvParser.pas.
I’ve also included the source code for a test bed that uses DUnit to test elements of the example. I cannot stress enough how good an idea it is to learn to use unit tests. The gains from writing tests are immediate, and I recommend that you investigate them, if you haven’t already.
Next we’ll need to decide how to read XML files. We will not attempt to write a full-blown XML parser, so we’ll restrict ourselves to a small subset of the language. We’ll also write our own parser, so the grammar can be kept simple for this example. However, the rules are more complex than those defining CSV files, and we in fact have a small language.
Processing a programming language normally requires several processes. The lowest level is that of lexical analyser, which breaks up the stream of source text into tokens. Tokens are small pieces of text, which are passed to the parser, which then interprets them in the context of the language grammar. A grammar is a set of rules defining the syntax of a language. It does not define the behaviour, which is known as the semantics. The parser is responsible for reporting syntax errors.
Commonly, the output of the parser is an abstract syntax tree, which is made up of nodes representing elements of the language. The abstract syntax tree is then used for other operations, like type checking and code generation if the system is a compiler.
This whole subject is quite complex, but it has been well understood  for decades. The standard reference is the Dragon Book, so called because of the picture of the dragon on the cover . Also known as Aho, Sethi and Ullman, my edition was printed in 1986, and the information is still current (unusually, for our field). I’m going to refer you to that for an in-depth discussion of the lexical analyser and the recursive descent parser in the example. You might want to note the similarities between such a parser and the Interpeter pattern.
Looking through the pattern book, we find that the pattern used to deal with languages is Interpreter. We will see that it can be used for other things than just evaluating (or executing, if you like) programs, which is what we would normally expect from the use of the term.
The term “interpret” here is pretty broad. In a BASIC interpreter, it would mean to go through executing instructions in some runtime environment. However, we can use an interpreter for other things that require an understanding of the structure of a language.
This pattern works best if that structure is not too complex. Because we will define a class for every element of the grammar, the class hierarchy can get very large (usually very shallow and very wide). It can be a quite inefficient way to represent and work with the data. In my opinion, these are also the conditions under which recursive descent compilers are appropriate, so they can be a good match. But you would not write a Delphi compiler this way. The Dragon Book discusses more efficient methods.
However, for us, with our small grammar, the Interpreter pattern is fine.
We will not be able to parse all XML documents. In particular, we will ignore DTDs, attributes, the structure of the contents of a prolog, escaped characters (e.g. <) and empty element tags (e.g. <NothingHere/>. We will be able to cope with empty files, though.
I’ve used a variant of Backus Naur Form (BNF) to define the grammar. Here an empty string is denoted by ε, zero or more occurrences by * (called Kleene closure, in case you’re interested), one or more by + (positive closure), and zero or one by 0..1 (no known aliases).
XmlDoc ->Prolog0..1 TagList0..1 Prolog -><?xml PrologData?> TagList ->Node* Node ->StartTag [Data | TagList] EndTag StartTag -><TagName> EndTag -></TagName> PrologData ->[Any printable characters except <,>,/ and ? ]* Data ->[Any printable characters except <,> and / ]* TagName ->[Any printable characters except <,>,/,space]+
An example of the sort of file that we will be able to interpret is:
<?xml version="1.0"?> <List> <SomeStuff>Stuff 1 is here</SomeStuff> <SomeStuff>Stuff 2 is here</SomeStuff> <SomeStuff>Stuff 3 is here</SomeStuff> <SomeStuff>Stuff 4 is here</SomeStuff> </List>
The Interpreter pattern normally requires a client to build the syntax tree. In our case, this will be the XML parser in XmlParser.pas. Time doesn’t permit a detailed description of this, but briefly, we have tokens which will be single characters, or the end of document marker. The lexical analyser class extracts these, and passes them to the XML parser. Like all recursive descent parsers, this has a procedure declared for each element of the grammar. These procedures check for appropriate tokens and report errors if necessary, and call procedures corresponding to other grammatical structures when they should appear in the source text. This parser adds nodes to the syntax tree as necessary.
The syntax tree is the essence of the Interpreter pattern. Astute readers will notice that it is a special case of the Composite pattern. The basis of the tree is the abstract expression class, which defines an abstract method to perform the Interpret operation. In our case this will be a search and replace (I told you the definition could be pretty broad). We will allow the operation just in data or in both tags and data. This is where the requirement to understand the structure of the document comes in.
We then go through our grammar defining subclasses, for each grammar element. There are two types of classes, although I don’t see the point of defining them in code, as there is not normally any difference between them that can be inherited. The first type is for terminal expressions, which are grammar elements that cannot be reduced further. In our grammar these are the PrologData, Data and TagName elements. These classes in fact turned out to be so trivial that I ended up refactoring them out, and they are now just string properties of the relevant non-terminal expression classes.
There is one class for each of the other grammar elements. Besides implementing the SearchAndReplace method, these classes have as properties instances of other expression classes from which they are constructed. The declarations of the interpreter classes are as follows (ignore the Accept routine for now).
// Forward declaration of base visitor class TXmlInterpreterVisitor = class; // Abstract base expression class TXmlExpression = class(TObject) private protected function DoSearchAndReplace(const TargetStr,SearchStr,ReplaceStr : string) : string; public // Declaring these methods abstract forces descendant classes to implement them procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); virtual; abstract; procedure Accept(Visitor : TxmlInterpreterVisitor); virtual; abstract; end; TXmlStartTag = class(TXmlExpression) private FTagName : string; protected public procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property TagName : string read FTagName write FTagName; end; TXmlEndTag = class(TXmlExpression) private FTagName : string; protected public procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property TagName : string read FTagName write FTagName; end; TXmlTagList = class; TXmlNode = class(TXmlExpression) private FStartTag : TXmlStartTag; FData : string; FTagList : TXmlTagList; FEndTag : TXmlEndTag; public destructor Destroy; override; procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property StartTag : TXmlStartTag read FStartTag write FStartTag; property EndTag : TXmlEndTag read FEndTag write FEndTag; property Data : string read FData write FData; property TagList : TXmlTagList read FTagList write FTagList; end; TXmlTagList = class(TXmlExpression) private FList : TObjectList; function GetItem(Index : Integer) : TXmlNode; protected public constructor Create; destructor Destroy; override; function Add : TXmlNode; procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property Items[Index : Integer] : TXmlNode read GetItem; default; end; TXmlProlog = class(TXmlExpression) private FData : string; protected public procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property Data : string read FData write FData; end; TXmlDoc = class(TXmlExpression) private FProlog : TXmlProlog; FTagList : TXmlTagList; protected public destructor Destroy; override; procedure Clear; procedure SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean = False); override; procedure Accept(Visitor : TxmlInterpreterVisitor); override; property Prolog : TXmlProlog read FProlog write FProlog; property TagList : TXmlTagList read FTagList write FTagList; end; // Equates to Client in the Interpreter pattern TXmlInterpreter = class(TObject) private FXmlDoc : TXmlDoc; protected public constructor Create; destructor Destroy; override; property XmlDoc : TXmlDoc read FXmlDoc write FXmlDoc; end; EXmlInterpreterError = class(Exception);
Note how the class definitions follow the grammar. The only variation is TXmlTagList which includes a function to add new nodes to the list. Oh, and TXmlDoc has a method to allow us to clear the syntax tree. Note that any lists we define are of type TObjectList, and they are constructed such that they will free the items in the list when they themselves are destroyed.
If we have a look at a couple of examples of the SearchAndReplace method, we will see the power of this pattern. Here is the version in TXmlDoc:
procedure TXmlDoc.SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean); begin if Assigned(Prolog) then begin Prolog.SearchAndReplace(SearchStr,ReplaceStr,DoTags); end; if Assigned(TagList) then begin TagList.SearchAndReplace(SearchStr,ReplaceStr,DoTags); end; end;
All we do is call the same method on the elements that make up this expression, that is, the Prolog and TagList properties. In this case, since they are optional, we check if they’re assigned first. This is the case in the other classes whenever there is a non-terminal expression. Whenever there is a terminal expression, we can actually perform the operation. For instance, here is the end tag method:
procedure TXmlEndTag.SearchAndReplace(const SearchStr,ReplaceStr : string; DoTags : Boolean); begin if not DoTags then begin Exit; end; TagName := DoSearchAndReplace(TagName,SearchStr,ReplaceStr); end;
The DoSearchAndReplace method is declared in the base class, as it is used in several places.
There is one last participant that you may sometimes need, which is the context. This holds any global information that the interpreter needs. We haven’t got any, so there isn’t one in the example. If you do have one, it is normally passed as a parameter to the interpret operation methods.
And that’s it. Just define classes for each expression in the grammar, which have properties corresponding to the sub-expressions. To implement an interpret operation define an abstract method in the base expression class. This forces descendant classes to implement it. In each implementation, call the method on all the sub-expression properties. For more complex operations than our example, there may have to be work done in some of the methods as well. And although we didn’t need it, sometimes it’s useful to have a reference to the parent node.
You can see that extending the grammar is pretty easy; you just need new classes, each of which is very similar, so the implementation is quite easy. At least, this is true until the number of classes gets too high – you would have hundreds in a Delphi interpreter, for instance. There are a number of issues about how to handle child operations, too, that Interpreter has in common with Composite, but we won’t go into them here.
Also, if you need to add several ways to interpret the syntax tree, then you will find that the code is practically identical for each. In that case, it’s time to use a different pattern.
What the Visitor pattern does is move the operations on the tree (or other object structure) from the nodes of the tree to another class. This class needs enough information in the interface of each node to perform the operation, so sometimes this can mean more public properties and methods than you might like.
Visitors have been described in Delphi before so we won’t dwell overly on them. Essentially, what you need to do is declare a Visitor class, which declares a Visit operation for each node type in the object structure. Here’s the base visitor class for the XML interpreter:
TXmlInterpreterVisitor = class(TObject) private protected procedure Visit(Exp : TXmlStartTag); overload; virtual; procedure Visit(Exp : TXmlEndTag); overload; virtual; procedure Visit(Exp : TXmlNode); overload; virtual; procedure Visit(Exp : TXmlTagList); overload; virtual; procedure Visit(Exp : TXmlProlog); overload; virtual; procedure Visit(Exp : TXmlDoc); overload; virtual; public end;
Visit methods are virtual so that visitor descendants can choose which methods to implement - it may not be necessary to implement them all. I’ve used function overloading as the signature is of necessity different for each, but this is not essential, and you could use more explicit names e.g. VisitXmlStartTag. The advantage of the function overloading is that it makes the code a little easier to follow, in my opinion.
In the base expression class we need to define an abstract Accept method. As for search and replace, the Delphi compiler forces us to implement the method in all the expression classes. You can see from the TXmlDoc implementation that the code is practically identical to the SearchAndReplace method shown earlier:
procedure TXmlDoc.Accept(Visitor : TxmlInterpreterVisitor); begin Visitor.Visit(Self); if Assigned(Prolog) then begin Prolog.Accept(Visitor); end; if Assigned(TagList) then begin TagList.Accept(Visitor); end; end;
The only difference is that we call the Visit method of the Visitor, which is passed as a parameter. Actually, I cheated a bit in one other method too, in order to make pretty printing work better, but if I skip lightly over it you’ll never notice.
Once the visitor code is in place in the syntax tree code, adding new visitors does not require any more changes to that code, only the declaration of new visitor classes.
A concrete visitor class is defined in XmlInterpreterVisitors.pas, showing how to implement a pretty printer. The parser is quite capable of taking a very badly formatted XML file and creating the syntax tree. We will regenerate the document all nicely laid out, like the example XML we saw earlier.
The class definition is:
TXmlPrettyPrinter = class(TXmlInterpreterVisitor) private FList : TStringList; FIndent : Integer; function GetText : string; protected procedure AddString(AStr : string); public constructor Create; destructor Destroy; override; procedure Visit(Exp : TXmlStartTag); override; procedure Visit(Exp : TXmlEndTag); override; procedure Visit(Exp : TXmlNode); override; procedure Visit(Exp : TXmlProlog); override; procedure Clear; property Text : string read GetText; end;
As you can see, we don’t need to implement a visitor for every expression class, as not all of them require anything to be printed. In fact, it’s only those that have terminal sub-expressions that will get anything printed. That is, the start and end tags, the data in a node, and the prolog.
We'll keep track of the indent at each point, and on each new line we will prepend the correct number of spaces. I’ve decided to collect the newly formatted text in a TStringList as it will keep track of new lines for me. The GetText function just accesses the Text property of the list.
Some of the Visit methods are:
procedure TXmlPrettyPrinter.Visit(Exp : TXmlStartTag); begin AddString('<' + Exp.TagName + '>'); Inc(FIndent,IndentAmount); end; procedure TXmlPrettyPrinter.Visit(Exp : TXmlEndTag); begin Dec(FIndent,IndentAmount); AddString('</' + Exp.TagName + '>'); AddString(''); end; procedure TXmlPrettyPrinter.Visit(Exp : TXmlNode); begin if Exp.Data = '' then begin // Print an empty tag AddString('<' + Exp.StartTag.TagName + '/>'); end else begin AddString(Format('<%s>%s</%s>', [Exp.StartTag.TagName, Exp.Data, Exp.EndTag.TagName])); end; end;
On finding a start tag, we add the tag to the list, then increment the indent. On an end tag we do the reverse, decrementing first to bring it back into line with the start tag. We also add a blank line after the tag. As it happens, this will only be the case for tags surrounding XML collections, as the place I cheated is on individual data nodes. I arranged the TXmlNode.Accept routine so that if there is no TagList, the start and end tags are not visited, but are left to be dealt with in the node visitor method, as shown above. This is a cheat purely to let me print the tags and data on one line more easily.
Adding a new operation on the syntax tree is easy, we just add a new visitor (we could reimplement the search and replace this way, for instance). Now related operations are all in one class, and unrelated ones would be in different classes, rather than the Interpreter classes containing many unrelated operations in each class.
Visitor is a really nice pattern, and quite often useful. It is similar to Iterator in that it is used to traverse object structures, but Visitor also works when there is no common parent for the structure items. However, it does have some disadvantages. We mentioned breaking encapsulation earlier, but it can also make life difficult if you often need to add new elements to your structure. For instance, if we added new grammar rules, then we would need to add new methods to the base visitor, and check every concrete visitor to see if it also needed to reflect the changes.
So we can now read both XML and CSV files. It's time to start feeding them documents.
We need to store some information about the document we are looking at. In many patterns, the class that holds information used by the main participants in the pattern is called a context (we could have used one in Interpreter, remember?). In our case, the document class acting as the context, and is pretty simple:
TDocument = class(TObject) private FFileText : TStringList; FStrategy : TDocumentStrategy; function GetText : string; procedure SetText(const Value : string); function GetMemento : TDocumentMemento; procedure SetMemento(const Value : TDocumentMemento); protected public constructor Create; destructor Destroy; override; procedure OpenFile(const FileName : string); procedure CloseFile; procedure SearchAndReplace(const FindText,ReplaceText : string); procedure PrettyPrint; property Text : string read GetText write SetText; property Memento : TDocumentMemento read GetMemento write SetMemento; end;
The OpenFile method uses the LoadFromFile method of the stringlist field FFileText to load the text into its lines. The CloseFile method clears the lines. The lines can be accessed as a string via the Text property. The remaining property and methods will be discussed later when we see them being called. The code for our document class is in the Document.pas file.
The first thing we will look at is the OpenFile method:
procedure TDocument.OpenFile(const FileName : string); begin FFileText.LoadFromFile(FileName); // Could use Factory Method here, but for now, just inline the code to // create the new strategy object FreeAndNil(FStrategy); if ExtractFileExt(FileName) = '.csv' then begin FStrategy := TCsvStrategy.Create(Self); end else if ExtractFileExt(FileName) = '.xml' then begin FStrategy := TXmlStrategy.Create(Self); end; end;
Here you can see that we load the specified file using the stringlist’s file loading method. We then create a strategy object depending on the file extension. In our simple example we’re only going to deal with CSV and XML files.
To understand why we’ve done this, we need to look at the Strategy pattern. This pattern allows us to define several different algorithms for the same thing, each one in a class of its own, and choose between them by using an object of the relevant class. In our case, we’re interested in hiding the details of the search-and-replace and pretty printing from users our document.
procedure TDocument.SearchAndReplace(const FindText,ReplaceText : string); begin if Assigned(FStrategy) then begin FStrategy.SearchAndReplace(FindText,ReplaceText); end; end; procedure TDocument.PrettyPrint; begin if Assigned(FStrategy) then begin FStrategy.PrettyPrint; end; end;
As you can see, the implementation of the two methods is deferred to the strategy object. The base strategy class is defined as:
TDocumentStrategy = class(TObject) private FDocument : TDocument; protected property Document : TDocument read FDocument write FDocument; public constructor Create(ADocument : TDocument); virtual; procedure SearchAndReplace(const FindText,ReplaceText : string); virtual; abstract; procedure PrettyPrint; virtual; abstract; end;
This is an abstract class, because we want to force descendant classes to implement both the methods. It’s quite common for strategy objects to need to access properties of the context, and indeed that’s what we will need to do. To facilitate this, the constructor takes the document as a parameter. Note the use of the Self Encapsulate Field refactoring so descendent strategies can be declared in other units and still have access to the document, i.e. the document property is declared in the protected section.
In the file DocumentStrategy.pas you can see the implementations of the two strategy classes. A look at the two SearchAndReplace methods should give you some idea why we needed to use this pattern:
procedure TCsvStrategy.SearchAndReplace(const FindText,ReplaceText : string); begin Document.Text := StringReplace(Document.Text,FindText,ReplaceText,[rfReplaceAll,rfIgnoreCase]); end; procedure TXmlStrategy.SearchAndReplace(const FindText,ReplaceText : string); begin FParser.Parse(Document.Text,FInterpreter.XmlDoc); FInterpreter.XmlDoc.SearchAndReplace(FindText,ReplaceText,True); // Pretty print as well, just so we can get some output FVisitor.Clear; FInterpreter.XmlDoc.Accept(FVisitor); Document.Text := FVisitor.Text; end;
As you can see, the two are quite different. While it would be no great hassle for the user of the document to call the Delphi StringReplace procedure on a CSV file, the code for XML files is quite different. We can also use the same classes to hide the details of more than one algorithm, as we do for pretty printing.
The normal alternative to using Strategy is to subclass the context, in this case our document class. We could have a TCSVDocument and a TXMLDocument, for instance. But this mixes the implementation of the algorithms with the document, and can make the document classes difficult to maintain.
The class hierarchy can also be difficult to structure, particularly where there is more than one algorithm to consider. If one branch of the hierarchy needs to share an implementation from another branch, as well as one from its own, life gets a bit difficult.
You could also get the same behaviour by using case statements or lists of conditionals (if..then..else if…) in the TDocument class. In our trivial case, this might not appear too bad, but it quickly gets out of hand, and leads to the Switch Statements bad smell. See the Refactoring paper for why this is an undesirable state of affairs, and what to do to fix it.
There are some downsides, though. There are an increased number of objects in the system (but in my opinion, they will always be smaller and easier to maintain). The strategy and context can be closely coupled, as in our example. It is possible to remedy this by just passing the needed parameters in the strategy methods. I don’t have a big problem with a certain amount of coupling, and in any case, some is unavoidable in this pattern.
In our example, the context (i.e. document) created the strategies as needed, but it is also common for the client to create the relevant one and pass it to the context. So we could get the user interface code to create the strategy, for instance. I have found that I can always refactor that so that the context can create the strategy it needs, but maybe there are situations this is not possible or desirable. In either case, you can see that something else needs knowledge of the strategies in order to choose between them.
Variations of the pattern exist.One of the more useful ones is making the context have a default implementation of the algorithm, and only creating a strategy object in certain situations. We could have the document use the StringReplace procedure for all documents, for instance, only using something else in cases like XML files.
Note that this pattern is similar to the Template pattern, which encapsulates one algorithm in a class, and lets subclasses vary certain parts of that one algorithm. An example is a sorting algorithm, where the base class might implement a quicksort, and the subclasses can define the comparison function differently. We’ll see another example in the Command pattern.
The Command pattern is implemented in Delphi (since Delphi 4) in actions. We won’t use them, as I want to concentrate on the pattern. Actions are complex beasts, and this complexity might detract attention away from the points I want to make. There may well also be occasions where actions are not appropriate, so you may like to know how to roll your own Command structure.
We’re going to use Command for the last of the above options, to implement an undo/redo list, and also to shield our user interface code from having to know anything about documents or the operations on them. After we’ve discussed how to do that, you should be able to see how you could add logging, queues, and so on.
In its simplest form, the Command pattern consists of an abstract base class with an Execute method. Concrete subclasses are declared for each possible action, and the method implemented in each so as to carry out that action. Usually this requires a call to another class’s interface. For instance, in our example, one command would be to open a file. After prompting for a file name, the open command will call a document object’s OpenFile method. This second object is called the receiver. The object that asks the command to carry out a request is called the invoker. This is often something like a menu item or a button. We are going to do something a little different, though, as we’ll see later.
The declaration of our command class (in DocumentCommands.pas) is:
TDocumentCommand = class(TObject) private FDocument : TDocument; protected procedure DoExecute; virtual; abstract; procedure DoRollback; virtual; // Used Self Encapsulate Field refactoring here. Now descendant commands // can access the document, even if they are declared in other units property Document : TDocument read FDocument write FDocument; public constructor Create(ADocument : TDocument); procedure Execute; procedure Rollback; // Reverse effect of Execute end;
The implementation is:
constructor TDocumentCommand.Create(ADocument : TDocument); begin inherited Create; FDocument := ADocument; end; procedure TDocumentCommand.DoRollback; begin end; procedure TDocumentCommand.Execute; begin if Assigned(FDocument) then begin DoExecute; end; end; procedure TDocumentCommand.Rollback; begin if Assigned(FDocument) then begin DoRollback; end; end;
Let’s examine what’s going on here. We have two public methods, one to perform a command, and another to reverse it. We need this last one because we want to support undo. Note that both these methods check to see if the document is available before calling another, protected, method. This is a very simple example of the Template pattern. We don’t want programmers who are implementing new commands to have to remember to do that, so we will do it for them here, and they need to override the DoExecute and DoRollback methods instead.
The DoRollback method implementation is empty because a command might not be able to be undone (we are only going to support this on pretty printing and search-and-replace), so we don’t want to force subclasses to implement it by making the method abstract. However, we do want the command to be implemented, so the DoExecute method is abstract to force it to be overridden.
The constructor gets the document passed as a parameter, and that will be used as the receiver in our example. For instance, the Open command is implemented as follows:
procedure TOpenCommand.DoExecute; var FileName : string; begin if PromptForFileName(FileName,'XML files (*.xml)|*.xml|’ + ‘CSV files (*.csv)|*.csv') then begin FDocument.OpenFile(FileName); end; end;
Here we call the Delphi function PromptForFileName, and if the user doesn’t cancel the operation, we call the document’s OpenFile method to load the text. For other commands, it gets a little more complex, and we start to see the advantages of separating the command logic out from the object that invokes it.
Other advantages are that it is easy to add new commands, because existing classes don’t need to change. This makes it a lower risk modification to your code. You can also use the Composite pattern to make larger macro commands out of several other commands.
Although our command implementations all make use of a receiver to ultimately carry out the operation, that’s not always necessary. The GoF example is that of a command to launch another application. The command may well have enough information to do that itself (by calling ShellExecute, say), without having to delegate the task to another object. It may also be necessary sometimes for the command to find its receiver dynamically. If our example application used MDI, then we might need to go and find the current document, for instance.
Next, we’ll examine how the more complex commands work, but to do that we’ll need to know about a new pattern.
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.
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;
The 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. More than enough has been written in other places about this pattern, as it is one of the simplest. 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.
You’ve seen how we can use patterns to better organise and implement a reasonably complex example, admittedly one that is somewhat contrived in places. We have occasionally used a given pattern where it was not strictly necessary in order to see how that pattern might be used, rather than because it was the best choice in the circumstances. We have discussed a few of the patterns that are not often mentioned, but that I nonetheless use regularly.
Hopefully you have some feel about how to go about using patterns in your work. If you find yourself with no real ideas about how to design or implement a certain part of your application, or you find your code getting tortuous, may it’s time to have a browse through a pattern book.
Just remember that a pattern is not code, and that you almost certainly will have to change the code attached to this paper to get it to work in your applications.
Gamma, Erich et al. Design Patterns. Elements of Reusable Object-Oriented Software, 1995, Addison-Wesley ISBN 0-201-63361-2
Larman, Craig. Applying UML and Patterns, 2002 Prentice-Hall PTR ISBN 0-13-092569-1
Bucknall, Julian. The Tomes of Delphi: Algorithms and Data Structures, 2001, Wordware Publishing Inc ISBN 1-55622-736-1
Aho, Sethi and Ullman. Compilers: Principles, Techniques and Tools, 1986, Addison-Wesley ISBN 0-201-10194-7 Let’s call this ToD:ADS, or just ToD for short.
 Implementing something with “state” in the name is also a big clue.
 And, if I’m honest, to make it easier for me to match the Interpreter pattern to the parser output.
 By smarty-britches, at least
 Who says programmers have no imagination, eh?
 As you can imagine, this is not that difficult.
 So much for the light skipping.
 And if you think this is a good time to use interfaces, you need a good smack! It’s actually a very bad time.
 By a strange and eerie coincidence, one solution is to use the Strategy pattern.
 Of course, we could have used the same method in XML files, but we can now do search-and-replace without destroying XML tags by accident.
 Undo/redo are not stored on the command list because my brain hurt trying to work that out.