📄 tiofp documentation - the visitor pattern and the tiofp.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0064)http://www.techinsite.com.au/tiOPF/Doc/2_TheVisitorFramework.htm -->
<HTML><HEAD><TITLE>tiOFP Documentation - The Visitor pattern and the tiOFP</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312"><!-- InstanceBegin template="/Templates/TechInsite_Template.dwt" codeOutsideHTMLIsLocked="false" --><!-- InstanceBeginEditable name="doctitle" --><!-- InstanceEndEditable --><!-- InstanceBeginEditable name="head" --><!-- InstanceEndEditable --><LINK
href="tiOFP Documentation - The Visitor pattern and the tiOFP_files/TechInsite.css"
type=text/css rel=stylesheet>
<META content="MSHTML 6.00.3790.1830" name=GENERATOR></HEAD>
<BODY>
<SCRIPT type=text/javascript>function Go(){return}</SCRIPT>
<SCRIPT
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/MainMenu_TechInsite.js"
type=text/javascript></SCRIPT>
<SCRIPT
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/HVMenu.js"
type=text/javascript></SCRIPT>
<NOSCRIPT>Your browser does not support script</NOSCRIPT>
<DIV id=Container><IMG height=75 alt=""
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/Banner01.jpg"
width=600 border=0>
<TABLE cellPadding=5 valign="middle">
<TBODY>
<TR>
<TD>
<DIV id=pageheader1>TechInsite</DIV></TD>
<TD>
<DIV id=pageheader2>Home of the TechInsite Object Persistence
Framework<BR>Melbourne, Australia</DIV></TD></TR></TBODY></TABLE>
<HR>
<TABLE>
<TBODY>
<TR>
<TD>
<DIV id=HMenu style="POSITION: relative"></DIV></TD></TR></TBODY></TABLE><BR>
<HR>
<!-- InstanceBeginEditable name="Page title" -->
<H1>2, The Visitor Pattern and the tiOFP</H1><!-- InstanceEndEditable --><!-- InstanceBeginEditable name="Page body" -->
<H1>The aims of this chapter</H1>
<P>The aim of this chapter is to introduce the Visitor Pattern as one of the
core concepts of the TechInsite Object Persistence Framework (tiOPF). We shall
take a detailed look at the problem we are aiming to solve, then investigate
some alternative solutions it before introducing the Visitor. As we are
developing our Visitor framework, we shall come across another problem that will
need our attentions: How do we iterate over a collection of objects in a generic
way? This shall be studied too. </P>
<P>So, the aim of this chapter is to come up with a generic way of performing a
family of related tasks on some of the elements in a list. The task we perform
may be different depending on the internal state of each element. We may not
perform any tasks at all, or we may perform multiple tasks on multiple list
elements.</P>
<H2>Prerequisites</H2>
<P>The reader should have a good understanding of Delphi and be clear on the
concepts of object oriented programming with Object Pascal. The previous Chapter
‘Chapter #1 What is an OPF?’ will help understand some of the concepts of an
object persistence framework.</P>
<H2>The business problem we will work with as an example</H2>
<P>As an example, we will construct an address book application that enables us
to store peoples’ names and contact details. With the increase in the number of
different ways we can contact a person, our application must be flexible enough
to cater for new addresses types without the need for any re-engineering. (I
have memories of having to add a Phone_Mobile column to an application, only to
complete that batch of work to be asked to add an EMail column.). We need to
allow for two types of addresses: regular addresses like home, work or postal
and e-addresses like phone, fax, mobile, EMail, Web.</P>
<P>Our presentation layer has to have an Explorer / Outlook look and feel and we
need to make extensive use of Microsoft’s TreeView and ListView common controls.
The application must perform well and must not have the look and feel of a
conventional, form based client server app.</P>
<P>The main form of our application is shown below.</P>
<P><IMG height=265
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_Form01.jpg"
width=437> </P>
<P>A right mouse click on the tree will let you add or delete a person or
company. A right click on either of the list views will open a modal dialog and
let you edit, insert or delete an address or e-address.</P>
<P>The data shall be stored in a variety of formats, and we will look at how to
do this with the help of the Adaptor Pattern as described in chapter 7.</P>
<H2>Setting up the demo</H2>
<P>We shall start work with a simple collection of objects as an example. We
will create a list of people, the people will have two properties: Name and
EMailAdrs. To start off, the people will be added to the list in the list’s
constructor (then we shall progressively read them from a text file, then
Interbase database). This is an over simplified example, but it is sufficient to
use when discussing the problems that the Visitor pattern solve.</P>
<P>Create a new application, and add two classes in the main form’s interface
section: TPeople, which descends from the TObjectList and TPerson which descends
from TObject. The interface section will look like this:</P><PRE>TPeople = class( TObjectList )
public
constructor Create ;
end ;
TPerson = class( TObject )
private
FEMailAdrs: string
FName: string;
public
property Name : string read FName write FName ;
property EMailAdrs : string read FEMailAdrs write FEMailAdrs ;
end ;</PRE>
<P>In the constructor of the list, we shall create three instances of TPerson
like this:</P><PRE>constructor TPeople.Create;
var
lData : TPerson ;
begin
inherited ;
lData := TPerson.Create ;
lData.Name := 'Malcolm Groves' ;
lData.EMailAdrs := 'malcolm@madrigal.com.au' ; // (ADUG Vice President)
Add( lData ) ;
lData := TPerson.Create ;
lData.Name := 'Don MacRae' ; // (ADUG President)
lData.EMailAdrs := 'don@xpro.com.au' ;
Add( lData ) ;
lData := TPerson.Create ;
lData.Name := 'Peter Hinrichsen' ; // (Yours truly)
lData.EMailAdrs := 'peter_hinrichsen@techinsite.com.au' ;
Add( lData ) ;
end;</PRE>
<P>To start off, we are going to iterate over the list and perform two
operations on each list element. The operations will be similar, but not the
same. In this dumb, over simplified example we will call ShowMessage( ) on each
TPerson’s Name and EMailAdrs properties. To start this off, add two buttons to
the application’s main form: One to show each person’s name, the other to show
each person’s EMailAdrs. The form will look like this:</P>
<P><IMG height=105
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_Form02.gif"
width=210> </P>
<H2>Step #1. Hard code the iteration</H2>
<P>To show each person’s name, add the following code in the first buttons
OnClick event. </P><PRE>procedure TFormMain.btnShowNamesClick(Sender: TObject);
var
i : integer ;
begin
for i := 0 to FPeople.Count - 1 do
ShowMessage( TPerson( FPeople.Items[i] ).Name ) ;
end;</PRE>
<P>and to show each persons Email address, add the following code to the second
button’s OnClick event.</P><PRE>procedure TFormMain.btnShowEMailsClick(Sender: TObject);
var
i : integer ;
begin
for i := 0 to FPeople.Count - 1 do
ShowMessage( TPerson( FPeople.Items[i] ).EMailAdrs ) ;
end;</PRE>
<P>(I did say it was over simplified. We will be writing data out to a text
file, and saving it to a database soon.)</P>
<P>Now, there are several things I don’t like about this code:</P>
<UL>
<LI>We have two routines that do pretty much the same thing. The only
difference is the property we are hitting in ShowMessage( )
<LI>Iterating over a list becomes very tedious when you have to write this
code in hundreds of places in your application.
<LI>Having to type cast each element of the list as a TPerson is error prone.
What if a TAnimal found its way into the list? There is no mechanism to stop
this happening, and no mechanism to protect us from errors if it does.
</LI></UL>
<P>Next, we shall look at ways of improving this code by abstracting away the
iteration logic into a parent class.</P>
<H2>Step #2. Abstracting the iteration logic</H2>
<P>We want to abstract the iteration logic into a parent class. The iteration
logic is simple and looks like this:</P><PRE>for i := 0 to Flist.Count - 1 do
// Do something here...</PRE>
<P>Sounds like we are building an instance of the Iterator Pattern. GoF tell
about two kinds of Iterators: External Iterators and internal Iterators. The
external Iterator was shown to us at last year’s BorCon in Malcolm Groves
(famous) presentation on writing solid code. We shall use an internal Iterator
here because it makes it easier to iterate over the nodes of a tree, which is
what we will be ultimately wanting to do. We will be adding a method called
Iterate to our collection class. This method will be passed a procedural type
parameter that defines a task to be performed on each node of the collection. We
shall call this procedural type TDoSomethingToAPerson.</P>
<P>To do this, we firstly extend the interface of the demonstration with a
forward declaration of TPerson (because TDoSomethingToAPerson references
TPerson, and TPerson references TDoSomethingToAPerson). Next, create a procedure
type called TDoSomethingToAPerson, which takes a single parameter of type
TPerson. Procedural types allow you to treat procedures and functions as values
that can be assigned to variables or passed to other procedures and functions.
This way we can define a procedure to call ShowMessage( Person.Name ), and one
to call ShowMessage( Person.EMailAdrs ), then pass these procedures to the
generic iteration routine. We add the method DoSomething( pMethod :
TDoSomethingToAPerson ) to our list of TPeople. The finished interface section
will look like this:</P><PRE>TPerson = class ; // Forward declaration of TPerson,
// required by TDoSomethingToAPerson
TDoSomethingToAPerson = procedure( const pData : TPerson ) of object ;
TPeople = class( TObjectList )
public
constructor Create ;
procedure DoSomething( pMethod : TDoSomethingToAPerson ) ;
end ;</PRE>
<P>The implementation of DoSomething( ) will look like this:</P><PRE>procedure TPeople.DoSomething(pMethod: TShowMethod);
var
i : integer ;
begin
for i := 0 to Count - 1 do
pMethod( TPerson( Items[i] )) ;
end;</PRE>
<P>Now, to perform an operation on each element in the list, we must do two
things. Firstly define a method of type TDoSomethingToAPerson, and secondly call
DoSomething( ), passing the a pointer to our TDoSomethingToAPerson as a
parameter. The code to do this is shown below:</P><PRE>// Somewhere in the application, we must define this method
procedure TFormMain.DoShowName( const pData : TPerson ) ;
begin
ShowMessage( pData.Name ) ;
end;
// Then in the form, we implement the call like this
procedure TFormMain.btnMethodPointerShowNameClick(Sender: TObject);
begin
FPeople.DoSomething( DoShowName ) ;
end;</PRE>
<P>This is progress. We have introduced three layers of abstraction. The generic
iteration logic is contained in the list class. The business logic (implemented
as ShowMessage) is contained in another part of the application, and the GUI has
a single line call to kick off a process.</P>
<P>It is easy to imagine how we could replace the call to ShowMessage with a
call to a TQuery instance that runs some SQL to save the data contained in the
TPerson. The call to a TQuery might look like this:</P><PRE>procedure TFormMain.SavePerson( const pData : TPerson ) ;
var
lQuery : TQuery ;
begin
lQuery := TQuery.Create( nil ) ;
try
lQuery.SQL.Text := 'insert into people values ( :Name, :EMailAdrs )' ;
lQuery.ParamByName( 'Name' ).AsString := pData.Name ;
lQuery.ParamByName( 'EMailAdrs' ).AsString := pData.EMailAdrs ;
lQuery.Datababase := gAppDatabase ;
lQuery.ExecSQL ;
finally
lQuery.Free ;
end ;
end; </PRE>
<P>This introduces the problem of maintaining state. We have connected the
TQuery up to an application wide database called gAppDatabase. Where is this
going to be maintained? Also, we will very quickly get tired of creating the
TQuery, setting up the parameters, executing the query, then remembering to call
free. This block of code would be better wrapped up an a class which inherits
from an abstract that takes care of creating and freeing the TQuery, and other
chores like wiring it up to a TDatabase.</P>
<H2>Step #3. Instead of passing a method pointer, we will pass an object</H2>
<P>Passing an object to our generic iterate method solves the problem of
maintain state. We will call the object we pass a Visitor, and create an
abstract Visitor class called TPersonVisitor with a single method Execute. The
interface of our abstract visitor looks like this:</P><PRE>TPersonVisitor = class( TObject )
public
procedure Execute( pPerson : TPerson ) ; virtual ; abstract ;
end ;</PRE>
<P>Next, we will add a method called Iterate to the TPeople list. The interface
of TPeople now looks like this:</P><PRE>TPeople = class( TObjectList )
public
procedure Iterate( pVisitor : TPersonVisitor ) ;
end ;</PRE>
<P>The implementation of TPeople.Iterate looks like this:</P><PRE>procedure TPeople.Iterate(pVisitor: TPersonVisitor);
var
i : integer ;
begin
for i := 0 to Count - 1 do
pVisitor.Execute( TPerson( Items[i] ));
end;</PRE>
<P>The Iterate method of TPeople is passed a concrete instance of
TPersonVisitor. For each TPerson in the list, the execute method of the
TPersonVisitor is called with the TPerson as a parameter.</P>
<P>We create concrete instances of TPersonVisitor called TShowNameVisitor and
TShowEMailAdrsVistor that implement the specific behavior we require. This is
shown below for TShowNameVisitor:</P><PRE>// Interface
TShowNameVisitor = class( TPersonVisitor )
public
procedure Execute( pPerson : TPerson ) ; override ;
end ;</PRE><PRE>// Implementation
procedure TFormMain.btnVisitorNameClick(Sender: TObject);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -