More Design Patterns in Delphi

Author: Jim Cooper. Link to original: http://conferences.embarcadero.com/article/32129 (English).
Tags: Delphi, gof, pattern, паттерн, шаблон, шаблон проектирования Submitted by r3code 16.03.2010. Public material.
Статья о подходах к программированию, более известных как шаблоны (паттерны) проектирования в применении в языку Delphi.

Translations of this material:

into Russian: Больше шаблонов проектирования на Delphi. 57% translated in draft.
Submitted for translation by r3code 16.03.2010

Text

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.

Jim Cooper is the UK User Group?s Technical Leader, and the architect of Tabdee Ltd's TurboSync Palm conduit components. He has used Delphi since its first release, and has long had an interest in developing for PDAs. An Australian living in England (temporarily, according to his wife), he has written for The Delphi Magazine and spoken at conferences in Europe, the UK and the US. He is now a consultant with Falafel Software.

More Design Patterns

Jim Cooper Falafel Software [email protected] www.falafelsoft.com

Introduction

The Example

Parsing CSV Files

State Pattern

Implementation

Parsing XML Files

Interpreter Pattern

Grammar

Implementation

Visitor Pattern

Documents

Strategy Pattern

Command Pattern

Memento Pattern

Facade Pattern

Summary

References

Introduction

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.

Well 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 dont use these examples that way, ok?

The Example

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 didnt start life that way. Some of the refactorings used to clean it up are used as examples in the Refactorings paper.

Parsing CSV Files

The first thing we will decide is how to read CSV files. Just so we all know what were 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 Bucknalls excellent Tomes of Delphi: Algorithms and Data Structures>[1] 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. Were just going to look at the finished product here.

State Pattern

Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class

GoF

The State pattern is used when an objects 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.

Implementation

The interface section source code for the State pattern code to parse CSV files is:

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

Pages: ← previous Ctrl next
1 2 3 4 5 6 7