⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tiofp documentation - the visitor pattern and the tiofp.htm

📁 tiOPF 面向对象的数据库持久层持久层开发的框架
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<!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 + -