📄 tiofp documentation - the visitor pattern and the tiofp.htm
字号:
useful values as parameters including the data object under the TListView, as
well as the usual sender and TListItem properties. The edit dialog implements a
class procedure called Execute that takes the data object to be edited as a
parameter. The dialog we shall build uses the TechInsite TPersistent aware
controls, and looks like this:</P>
<P><IMG height=141
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_Form04.gif"
width=294> </P>
<P>No rocket science here except that a one liner as calls the dialog described
above. The implementation of the class method Execute( ) looks like this:</P><PRE>class procedure TFormEditPerson.Execute(pData: TPersistent);
var
lForm : TFormEditPerson ;
begin
lForm := TFormEditPerson.Create( nil ) ;
try
lForm.Data := pData ;
lForm.ShowModal ;
finally
lForm.Free ;
end ;
end;</PRE>
<P>This code can be implement in an abstract form, so we only have to write it
once. The only code we do have to write for a dialog is contained in the
SetData( ) method and looks like this:</P><PRE>procedure TFormEditPerson.SetData(const Value: TPersistent);
begin
FData := Value;
paeName.LinkToData( FData, 'Name' ) ;
paeEMailAdrs.LinkToData( FData, 'EMailAdrs' ) ;
end;</PRE>
<P>Now that we have a basic form to browse a list of objects and to edit the
objects, we can take a look at how to save the data to a text file.</P>
<H2>Step #9 Create an abstract text file visitor</H2>
<P>We shall create an abstract text file visitor, that knows how to read and
write to a file. There are three ways we could do this: Using Delphi’s file
management routines (like AssignFile() and ReadLn()); using a TStringStream or
TFileStream, or using a TStringList.</P>
<P>The file management routines are good, but have been around since the early
days of Pascal and are a little dated (IMHO). The Stream approach is better
because it is very object oriented and makes it easy to move data from one
stream to another. Compression and encryption are also made a piece of cake,
however reading from a TFileStream or TStringStream line by line is a chore. The
TStringList gives us the LoadFromFile() and SaveToFile() methods which are easy
to use, but rather slow on large files. We shall use the TStringList as the
interface to a text file here because it is easy to use. I suggest looking at
developing a TStream based solution for use in real life though.</P>
<P>The interface of our abstract file visitor looks like this:</P><PRE>TVisFile = class( TVisitor )
protected
FList : TStringList ;
FFileName : TFileName ;
public
constructor Create ; override ;
destructor Destroy ; override ;
end ;</PRE>
<P>and the implementation looks like this:</P><PRE>constructor TVisFile.Create;
begin
inherited;
FList := TStringList.Create ;
if FileExists( FFileName ) then
FList.LoadFromFile( FFileName ) ;
end;</PRE><PRE>destructor TVisFile.Destroy;
begin
FList.SaveToFile( FFileName ) ;
FList.Free ;
inherited;
end;</PRE>
<P>The value of FFileName is set in the constructor of the concrete class, which
is hardly a good long term solution but it is sufficient to get us working here.
The class diagram of the hierarchy we are building is shown below.</P>
<P><IMG height=354
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_UMLFileVisitors.gif"
width=576> </P>
<P>The next step is to create two descendants of TVisFile: one for managing
fixed length text files (TXT) and the other for managing comma separated value
(CSV) files. The implementation of these is shown below:</P><PRE>constructor TVisCSVFile.Create;
begin
FFileName := 'Names.CSV' ;
inherited;
end;</PRE><PRE>constructor TVisTXTFile.Create;
begin
FFileName := 'Names.TXT' ;
inherited;
end;</PRE>
<P>As you can see, the only implementation change is to set the value of
FFileName in the constructor.</P>
<H2>Step #10. Create the text file read and save Visitors</H2>
<P>We implement two concrete visitors: one to read TXT files and one to save to
TXT files. The TXT read visitor has the AcceptVisitor and Execute methods
overridden. AcceptVisitor checks the object being visited is an instance of
TPeople with the one line call result := pVisited is TPeople. The implementation
of TVisTXTRead.Execute is shown below:</P><PRE>procedure TVisTXTRead.Execute(pVisited: TVisited);
var
i : integer ;
lData : TPerson ;
begin
if not AcceptVisitor( pVisited ) then
Exit ; //==>
TPeople( pVisited ).List.Clear ;
for i := 0 to FList.Count - 1 do
begin
lData := TPerson.Create ;
lData.Name := Trim( Copy( FList.Strings[i], 1, 20 )) ;
lData.EMailAdrs := Trim( Copy( FList.Strings[i], 21, 80 )) ;
TPeople( pVisited ).Add( lData ) ;
end ;
end;</PRE>
<P>This visitor clears the internal list in the TPeople class that was passed to
the execute method, then scans the internal TStringList and creates an instance
of TPerson for each line it finds. The Name and EmailAdrs properties are
extracted from the space padded lines in the TStringList.</P>
<P>The TXT file save visitor performs the reverse of this operation. In the
constructor the internal TStringList is emptied of data, then AcceptVisitor
checkes if the object being visited is an instance of TPerson. This is an
important difference between the read visitor and the save visitor. The read
visitor works on an instance of TPeople and the save visitor works on an
instance of TPerson. Strange and confusing? Yes, on the surface it looks like it
would be easier to read to and save from a TPeople, scanning the TStringList and
writing to the TPeople for a read and scanning the TPeople and writing to the
TStringList for a Save. This would defenitely be easier when persisting to a
text file, however when saving to a relational database, we only want to execute
SQL for objects that have changed (newly created, delted or edited) so we want
to have the flexiability to test an object’s internal state in the AcceptVisitor
method before calling Execute. So, TVisTXTSave.AcceptVisitor( ) has the one line
call result := pVisited is TPerson.</P>
<P>TVisTXTSave.Execute simply writes the Name and EMailAdrs properties to the
TStringList after padding them with spaces. The implementation of Execute is
shown below:</P><PRE>procedure TVisTXTSave.Execute(pVisited: TVisited);
begin
if not AcceptVisitor( pVisited ) then
Exit ; //==>
FList.Add( tiPadR( TPerson( pVisited ).Name, 20 ) +
tiPadR( TPerson( pVisited ).EMailAdrs, 60 )) ;
end;</PRE>
<P>The tiPadR( ) function will pad a string with spaces.</P>
<H2>Step #11. Create the CSV file read and save Visitor</H2>
<P>The CSV file read and save visitors are similar to the TXT file versions,
except that instead of using Copy() and tiPadR() to extract and write the
object’s properties, we use tiToken( ) which is a function that can break a
delimited string up into it’s individual pieces. (tiToken( ) was cloned from a
Clipper function of the same name when I moved to Delphi 1 in the early days of
Delphi). The implementation of TVisCSVRead.Execute and TVisCSVSave.Execute are
shown below:</P><PRE>procedure TVisCSVSave.Execute(pVisited: TVisited);
begin
if not AcceptVisitor( pVisited ) then
Exit ; //==>
// Build up a string comprising the Name and EmailAdrs separated by a comma
FList.Add( TPerson( pVisited ).Name + ',' +
TPerson( pVisited ).EMailAdrs ) ;
end;
procedure TVisCSVRead.Execute(pVisited: TVisited);
var
i : integer ;
lData : TPerson ;
begin
if not AcceptVisitor( pVisited ) then
Exit ; //==>
TPeople( pVisited ).List.Clear ;
for i := 0 to FList.Count - 1 do
begin
lData := TPerson.Create ;
// Parse a string of the form ’Name,EmailAdrs’
lData.Name := tiToken( FList.Strings[i], ',', 1 ) ;
lData.EMailAdrs := tiToken( FList.Strings[i], ',', 2 ) ;
TPeople( pVisited ).Add( lData ) ;
end ;
end;</PRE>
<P>All that remains to be done now is to register the four visitors with the
visitor manager, and test the application’s ability to read from one file format
and to save to another. We register the Visitors in the unit’s initialization
section like this:</P><PRE>initialization
gVisitorMgr.RegisterVisitor( 'CSVRead', TVisCSVRead ) ;
gVisitorMgr.RegisterVisitor( 'CSVSave', TVisCSVSave ) ;
gVisitorMgr.RegisterVisitor( 'TXTRead', TVisTXTRead ) ;
gVisitorMgr.RegisterVisitor( 'TXTSave', TVisTXTSave ) ;
end.</PRE>
<P>This means we can build a GUI like this, with the ability to read and write
to and from different file formats.</P>
<P><IMG height=193
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_Form05.jpg"
width=307> </P>
<P>Extra file formats can be easilly added by registering the reader and writer
Visitors with the Visitor manager.</P>
<H2>Step #12. One final refactor</H2>
<P>To prepare the code base for work with a relational database we will refactor
it one last time to break the Visitor classes, abstract business classes,
concrete business classes, abstract persistence classes and concrete persistence
classes into separate units. When this is done, our demo application will have
the following units and classes:</P>
<TABLE cellSpacing=0 cellPadding=0>
<TBODY>
<TR class=Normal>
<TD vAlign=top width=130>
<P><STRONG>Unit name </STRONG></P></TD>
<TD vAlign=top width=170>
<P><STRONG>Purpose </STRONG></P></TD>
<TD vAlign=top width=189>
<P><STRONG>Classes </STRONG></P></TD></TR>
<TR class=Normal>
<TD vAlign=top width=130>
<P>tiPtnVis.pas </P></TD>
<TD vAlign=top width=170>
<P>Abstract Visitor and Visited classes</P></TD>
<TD vAlign=top width=189>
<P>TVisitor <BR>TVisited </P></TD></TR>
<TR class=Normal>
<TD vAlign=top width=130>
<P>tiPtnVisMgr.pas </P></TD>
<TD vAlign=top width=170>
<P>The Visitor Manager</P></TD>
<TD vAlign=top width=189>
<P>TVisitorMapping <BR>TVisitorMgr </P></TD></TR>
<TR class=Normal>
<TD vAlign=top width=130>
<P>tiPtnVisPerObj.pas </P></TD>
<TD vAlign=top width=170>
<P>Abstract business objects and business object list classes</P></TD>
<TD vAlign=top width=189>
<P>TPerObjAbs<BR>TPerListAbs </P></TD></TR>
<TR class=Normal>
<TD vAlign=top width=130>
<P>People_BOM.pas </P></TD>
<TD vAlign=top width=170>
<P>Concrete business objects</P></TD>
<TD vAlign=top width=189>
<P>TPerson<BR>TPeople </P></TD></TR>
<TR class=Normal>
<TD vAlign=top width=130>
<P>People_Srv.pas </P></TD>
<TD vAlign=top width=170>
<P>Concrete persistent classes</P></TD>
<TD vAlign=top width=189>
<P>TVisFile<BR>TVisTXTFile<BR>TVisCSVFile<BR>TVisCSVSave<BR>TVisCSVRead<BR>TVisTXTSave<BR>TVisTXTRead
</P></TD></TR></TBODY></TABLE>
<P>There are two things to notice about my file naming standard:</P>
<UL>
<LI>All the units that contain visitor related abstract classes are prefixed
tiPtnVis. This was based on an idea I had a while ago where by all units that
contain pattern implementations would be prefixed tiPtn and placed in their
own directory. This was a great idea at the time, but does not really work in
practice as multiple patterns can be found in several units. We are
unfortunately stuck with this standard because it would be to much work to
change the existing systems that use the framework.<BR>
<LI>The People classes are implemented in two units: People_BOM.pas and
People_Srv.pas. The _BOM unit contains the business object model, the _Srv.pas
unit contains the persistence classes (_Srv stands for server side class).
Sometimes, there will also be a _Cli.pas unit which will contain client side
code like a Singleton instance of a class, or a thread with a progress bar to
perform some lengthy database access and to keep the user amused while it is
taking place. </LI></UL>
<H2>Summary</H2>
<P>In this chapter we have looked at the problem of iterating over a collection
of objects, that may or may not be of the same type. We have used GoF’s Visitor
pattern to perform an operation on 0 to many of the objects then looked at how
the text file read and write visitors can be developed to persist a collection
of objects to a text file.</P>
<P>We have created an abstract TVisitor and TVisited class which define the
visitor-visited relationship. The TVisited descendants know how to pass an
instance of TVisitor over each object that it owns.</P>
<P>We have descended from TVisitedAbs and created an abstract business object
class, and abstract business object collection class.</P>
<P>We have create a Visitor manager which allows visitors that perform different
tasks to be registered and called by name. This allows us work read and write
from different file formats.</P>
<H2>The next session</H2>
<P>In the next session, we shall develop a family of visitors that will read and
write to a relational database. We shall also extend the abstract business
objects so they are able to pass a visitor over more complex data structures. <A
href="http://www.techinsite.com.au/tiOPF/Doc/3_TheVisitorAndSQLDatabases.htm">The
next section can be read here.</A><BR></P><!-- InstanceEndEditable -->
<DIV id=Footer>(c) TechInsite Pty Ltd, Melbourne, Australia.
www.techinsite.com.au</DIV></DIV><!-- InstanceEnd --></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -