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

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

📁 tiOPF 面向对象的数据库持久层持久层开发的框架
💻 HTM
📖 第 1 页 / 共 3 页
字号:
var
  lVis : TPersonVisitor ;
begin
  lVis := TShowNameVisitor.Create ;
  try
    FPeople.Iterate( lVis ) ;
  finally
    lVis.Free ;
  end ;
end;</PRE>
<P>From this we can see that while we have solved one problem, we have created 
another. The iteration logic is abstracted away to the list class. We are using 
an object to define the special behavior which will allow us to maintain some 
state information, but we have blown the number of lines of code to implement 
this behavior from one as in:</P><PRE>procedure TFormMain.btnMethodPointerShowNameClick(Sender: TObject);
begin
  FPeople.DoSomething( DoShowName ) ;
end;</PRE>
<P>to the nine lines in the preceding block of code. What we need now is a 
visitor manager to take care of the construction and destruction of our visitor 
classes. We will potentially have several per operation so the visitor manager 
will handle named lists of visitors and call each visitor in the list in turn 
against the elements in the list. This will become essential as we move towards 
using the Visitor to persist data to a relational database because a simple save 
may require three different SQL statements: CREATE, UPDATE and DELETE.</P>
<H2>Step #4. Abstract the Visitor logic</H2>
<P>Before we go any further, we must abstract the Visitor functionality away 
from the business objects so we never have to touch it again. We will do this in 
three stages. We will create abstract TVisitor and TVisited classes, then we 
will create an abstract business object, and business object list that will 
descend from TVisited. We will then re-factor our TPerson and TPeople classes to 
descend from the newly created abstract classes.</P>
<P>The class diagram of what we are aiming to build looks like this:</P>
<P><IMG height=375 
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_UMLVisitors.gif" 
width=313> </P>
<P>The TVisitor has two methods, AcceptVisitor( ) and Execute( ). Both taking a 
single parameter of type TVisited. The TVisited has a single method called 
Iterate( ) which takes a single parameter of type TVisitor. TVisited.Iterate( ) 
calls the Execute method on the Visitor that is passed as a parameter against 
itself, and if it contains other object, against each one of the too. The 
function TVisitor.AcceptVisitor is necessary because we are building a generic 
framework. It will be possible to pass a visitor that is designed for handling 
TPeople a concrete instance of, say a TDog and we must have a mechanism for 
preventing this from causing an access violation. The TVisited descends from 
TPersistent because down the track, we will be implementing some functionality 
that requires RTTI. The interfaces of TVisitor and TVisited are shown below:</P><PRE>TVisitor = class( TObject )
protected
  function AcceptVisitor( pVisited : TVisited ) : boolean ; virtual ; abstract ;
public
  procedure Execute( pVisited : TVisited ) ; virtual ; abstract ;
end ; // Both AcceptVisitor and Execute must be implemented in the concreate</PRE><PRE>TVisited = class( TPersistent )
public
  procedure Iterate( pVisitor : TVisitor ) ; virtual ;
end ;</PRE>Both TVisitor.AcceptVisitor and TVisitor.Execute must be implemented 
in the concrete class. The implementation of TVisited.Iterate, which contains a 
call to TVisitor.Execute, is shown below:
<P></P><PRE>procedure TVisited.Iterate(pVisitor: TVisitor);
begin
  pVisitor.Execute( self ) ;
end;</PRE>
<H2>Step #5. Create an abstract business object, and abstract list object</H2>
<P>We require two more abstract classes in our framework: An abstract business 
object, and a list container for the abstract business object. We shall call 
these TPerObjAbs and TPerVisList and the interface of these classes is shown 
below:</P><PRE>TPerObjAbs = class( TVisited )
private
public
  constructor Create ; virtual ;
end ;</PRE>
<P>We will be adding significantly to TPerObjAbs when we look in more detail at 
the business object framework, but for the time being, we just add a virtual 
constructor so we can uniformly override the constructor in descendent 
classes.</P>
<P>We want our list class, TPerVisList to descend from TVisited so the generic 
visitor behavior can be implemented (actually, we want it to descend from 
TPerObjAbs for reasons we will discuss later). Ideally, we would use interfaces 
to give our list class the iteration behavior, but much of this code base 
predates the popularity of interfaces, and I have not faced up to the task of 
re-factoring to take advantage the benefits they can offer.</P>
<P>To create a list class which descends from TVisited and TPerObjAbs, we shall 
use object containment. The interface of TPerVisList is shown below and the 
implementation is pretty much what you would expect.</P><PRE>TPerVisList = class( TPerObjAbs )
private
  FList : TObjectList ;
  function GetList: TList;
public
  constructor Create ; override ;
  destructor Destroy ; override ;
  property List : TList read GetList ;
  procedure Iterate( pVisitor : TVisitor ) ; override ;
  procedure Add( pData : TObject ) ;
end ;</PRE>
<P>The most important method in this class is the overridden procedure Iterate. 
In the abstract class TVisitor, iterate is implemented as the one line call 
pVisitor.Execute( self ). In the TPerVisList it is implemented like this:</P><PRE>procedure TPerListAbs.Iterate(pVisitor: TVisitor);
var
  i : integer ;
begin
  inherited Iterate( pVisitor ) ;
  for i := 0 to FList.Count - 1 do
    ( FList.Items[i] as TVisited ).Iterate( pVisitor ) ;
end;</PRE>
<P>This is an important core concept. We now have two abstract business objects 
TPerObjAbs and TPerVisList. Both have an iterate method that is passed an 
instance of a TVisitor as a parameter. In each case, the TVisitor’s Execute 
method is called with self as a parameter. This call is made via inherited at 
the top of the hierarchy. For the TPerVisList class, each object in the owned 
list also has its Iterate method called with the visitor begin passed as the 
parameter. This ensures that all objects in the hierarchy are touched by the 
Visitor.</P>
<H2>Step #6. Create a Visitor manager</H2>
<P>Now, back to the original problem we created for our selves in step #3. We 
don’t want to be spending all our time creating and destroying visitors. The 
solution is in the Visitor Manager.</P>
<P>The Visitor Manager performs two main tasks: It maintains a list of 
registered visitors (visitors are registered in the implementation section of 
the unit where they are declared.); and calls a group of visitors that are 
registered with a given command name against the data object it is passed.</P>
<P>To implement the Visitor manager, we will define three more classes: The 
TVisitorClass, TVisitorMapping and the TVisitorMgr.</P>
<P>The TVisitorClass is a class reference type that will hold an instance 
TVisitor’s class. I find the help text description of class references a little 
confusing. The best way to understand them is with an example. Lets say we have 
our abstract Visitor class TVisitor, and a Visitor class reference type 
TVisitorClass. We also have a concrete Visitor called TSaveVisitor. The 
TVisitorClass type is declared like this:</P><PRE>TVisitorClass = class of TVisitor ;</PRE>
<P>This lets us write code like this:</P><PRE>procedure ExecuteVisitor( const pData : TVisited ; const pVisClass : TVisitorClass ) ;
var
  lVisitor : TVisitor ;
begin
  lVisitor := pVisClass.Create ;
  try
    pData.Iterate( lVisitor ) ;
  finally
    lVisitor.Free ;
  end ;
end ;</PRE>
<P>We pass two parameters to this procedure; pData which is an instance of 
TVisited (like our TPeople), a TVisitorClass, which could be TShowNameVisitor or 
TShowEMailAdrsVisitor. This procedure takes care of the tedious business of 
creating the visitor, calling iterate, then freeing the visitor when done.</P>
<P>The second class we create for our visitor manager is called TVisitorMapping. 
It is a simple data structure to hold two pieces of information: a TVisitorClass 
and a string called Command. The interface of TVisitorMapping is shown 
below:</P><PRE>TVisitorMapping = class( TObject )
private
  FCommand: string;
  FVisitorClass: TVisitorClass;
public
  property VisitorClass : TVisitorClass read FVisitorClass write FVisitorClass ;
  property Command : string read FCommand write FCommand ;
  end ;</PRE>
<P>The final class we create is the TVisitorMgr. When we register a Visitor with 
the Visitor Manager, an instance of TVisitorMapping is created and added to the 
list inside the TVisitorMgr. The command and VisitorClass properties are set 
which allows us to execute a group of visitors identified by a string. The 
interface of the TVisitorManager is shown below:</P><PRE>TVisitorMgr = class( TObject )
private
  FList : TObjectList ;
public
  constructor Create ;
  destructor Destroy ; override ;
  procedure RegisterVisitor( const pCommand : string ; pVisitorClass : TVisitorClass ) ;
  procedure Execute( const pCommand : string ; pData : TVisited ) ;
end ;</PRE>
<P>The key methods here are RegisterVisitor and Execute. RegisterVisitor is 
called in the implementation section of the unit where the Visitor is defined 
and is typically called like this:</P><PRE>initialization
   gVisitorMgr.RegisterVisitor( 'show', TShowNameVisitor ) ;
   gVisitorMgr.RegisterVisitor( 'show', TShowEMailAdrsVisitor ) ;</PRE>
<P>The implementation of RegisterVisitor is shown below (this code is much the 
same as the code found in a Delphi implementation of the Factory Pattern)</P><PRE>procedure TVisitorMgr.RegisterVisitor( const pCommand: string; 
const 
  pVisitorClass: TVisitorClass);
var
  lData : TVisitorMapping ;
begin
  lData := TVisitorMapping.Create ;
  lData.Command := pCommand ;
  lData.VisitorClass := pVisitorClass ;
  FList.Add( lData ) ;
end;</PRE>
<P>The other important method in the TVisitorMgr is Execute. Execute takes two 
parameters, the command name which identifies the family of visitors to be 
executed, and the data object which is at the top of the tree to be iterated 
over. The implementation of Execute is shown below:</P><PRE>procedure TVisitorMgr.Execute(const pCommand: string; const pData: TVisited);
var
  i : integer ;
  lVisitor : TVisitor ;
begin
  for i := 0 to FList.Count - 1 do
    if SameText( pCommand, TVisitorMapping( FList.Items[i] ).Command ) then
    begin
      lVisitor := TVisitorMapping( FList.Items[i] ).VisitorClass.Create ;
    try
      pData.Iterate( lVisitor ) ;
    finally
      lVisitor.Free ;
    end ;
  end ;
end;</PRE>
<P>To execute both the ShowName and ShowEMailAdrs visitors (the ones we 
registered above), one after the other we would make the folling call to the 
Visitor manager.</P><PRE>gVisitorMgr.Execute( 'show', FPeople ) ;</PRE>
<P>Next, we will create some persistent Visitors that will let us make calls 
like</P><PRE>// To read from a text file
gVisitorMgr.Execute( 'read', FPeople ) ;
// To save to a text file
gVisitorMgr.Execute( 'save', FPeople ) ;</PRE>
<P>but first we will use the tiListView and tiPerAwareControls to create a GUI 
to use while editing the list of TPeople.</P>
<H2>Step #7. Re-factor the BOM to descend from our abstract BO and BO list</H2>
<P>We shall start be re-factoring our business objects TPeople and TPerson to 
descend from the abstract classes TPerVisList and TPerObjAbs. This means that 
our iteration logic will be wrapped up in the parent class, never to be touched 
again. It also makes it possible to use the business objects with the visitor 
manager.</P>
<P>The new interface of the TPeople looks like this:</P><PRE>TPeople = class( TPerVisList ) ;</PRE>
<P>In fact, there is no code to implement in the class TPeople. In theory, we 
could do without the concrete class TPeople and store our TPerson objects in an 
instance of TPerVisList. We go to the trouble of creating the concrete class 
TPeople however because we use the line result := Visited is TPeople in the 
AcceptVisitor method. We need to know the concrete class of the collection so we 
can decide if we want to process it with a visitor.</P>
<P>The TPerson class now descends from TPerObjAbs We must also moved the 
persistent properties in the TPerson class from public to published. This allows 
us to use RTTI to present the data with the TPersistent aware controls, and to 
use the mapping framework we shall look at later to reduce the amount of code we 
must write to save the objects to a database. The interface of TPerson now looks 
like this:</P><PRE>TPerson = class( TPerObjAbs )
private
  FEMailAdrs: string;
  FName: string;
published
  property Name : string read FName write FName ;
  property EMailAdrs : string read FEMailAdrs write FEMailAdrs ;
end ;</PRE>
<H2>Step #8. Create a GUI</H2>
<P>Download and install the TechInsite persistence framework code from 
http://www.techinsite.com.au/tiopf/download.htm and following the instructions 
at www.techinsite.com.au/tiopf/gettingstarted.htm to install the TPersistent 
aware controls into your component pallet. When you have finished, your 
component pallet should look like this:</P>
<P><IMG height=77 
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_tiOFPComponentPallet.jpg" 
width=576> </P>
<P>Drop a TtiListView on the application’s main form then In the form’s 
constructor create an instance of TPeople and store it to a variable FPeople. 
Assign the data property of the TtiListView like this:</P><PRE>procedure TFormMain_VisitorManager.FormCreate(Sender: TObject);
begin
  FPeople := TPeople.Create ;
  LV.Data := FPeople.List ;
end;</PRE>
<P>Select the TtiListView and in the object inspector change its name to LV. Set 
the VisibleButtons property to [tiLVBtnVisNew ,tiLVBtnVisEdit, tiLVBtnVisDelete] 
then go to the Events tab of the Object Inspector where we will implement the 
OnEdit, OnInsert and OnDelete methods.</P>
<P>Run the application and you should have a form that looks like this:</P>
<P><IMG height=207 
src="tiOFP Documentation - The Visitor pattern and the tiOFP_files/2_TheVisitorFramework_Form03.gif" 
width=366> </P>
<P>(The TtiListView will automatically detect a TList of TPersistent descendants 
and display the published properties as columns in the list)</P>
<P>The three small buttons on the TtiListView fire events called OnEdit, 
OnInsert and OnDelete when clicked. An edit dialog will be implemented under the 
OnEdit and OnInsert events so we can play around with the data. The code in the 
OnEdit event would typically look like this:</P><PRE>procedure TFormMain_VisitorManager.LVItemEdit( pLV: TtiCustomListView;
   pData: TPersistent;
   pItem: TListItem);
begin
  TFormEditPerson.Execute( pData ) ;
end ;</PRE>
<P>The things to notice are that the OnEdit event on the TtiListView passes some 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -