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

📄 tiofp documentation - building an abstract bom with the composite pattern.htm

📁 tiOPF 面向对象的数据库持久层持久层开发的框架
💻 HTM
📖 第 1 页 / 共 5 页
字号:
    ( FList.Items[i] as TVisited ).Iterate( pVisitor ) ;
end;</PRE>
<P>This approach is fine as long as we keep our class hierarchy linear like 
this:</P>
<P><IMG height=155 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image001_0003.gif" 
width=313> </P>
<P>The TPeople own a list of TPerson(s) that own a list of TEAdrs(s). This can 
be implemented by chaining two TPerObjList classes together, with a TPerObjAbs 
tacked on the end.</P>
<P>If we call iterate at the top of the tree, passing a visitor like 
FPeople.Itereate( TVisDoSomething ) we will be guaranteed of the visitor 
touching all the elements in the hierarchy. But what if we want to create a 
class hierarchy like the one shown below, where each TPerson owns two lists: one 
of TEAdrs(s) and one of TAdrs(s)?</P>
<P><IMG height=139 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image001_0004.gif" 
width=314> </P>
<P>Within our Composite pattern framework, there are three ways of achieving 
this:</P>
<P>1. TPerson descends from TPerObjList (which gives us a holder for the TEAdrs 
objects) and we add another TObjectList to hold the TAdrs(s), like this:</P>
<P><IMG height=213 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image001_0005.gif" 
width=229> </P>
<P>2. TPerson descends from TPerObjAbs, and we add two TObjectList(s); One to 
hold the TEAdrs(s) and one to hold the TAdrs(s) like this:</P>
<P><IMG height=217 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image001_0006.gif" 
width=241> </P>
<P>3. We create two more classes: TEAdrsList, which holds TEAdrs(s); and 
TAdrsList, which holds TAdrs(s). We add an instance of TEAdrsList and TEAdrsList 
to the TPerson class, like this:</P>
<P><IMG height=223 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image001_0007.gif" 
width=396> </P>
<P>I tend to use (3) in real life systems because it allows me to tightly couple 
each list to the classes it will hold. This makes for a much more robust 
application from a programmer’s point of view.</P>
<P>In the following examples, we shall start by writing a Visitor to convert the 
objects to text so it is easier to look inside the class hierarchy for 
debugging. Next, shall implement (2) because it is an easier way to get started 
then move onto implementing (3) as we fine tune our iterate functionality.</P>
<H2>Delphi versions</H2>
<P>Before going any further, a word about Delphi versions. Most of the code we 
are going to look at over the next few sections was developed in Delphi 5 and 
uses some procedures that where added to TypInfo.pas with the release of version 
5. These procedures make it much easier to use RTTI than in previous Delphi 
versions so the examples will not work with Delphi four or below. At the time of 
writing, the framework has not been tested with Delphi 6, however other users 
have reported the need to change some unit names to get the code compiling.</P>
<H2>A Visitor to show a class hierarchy as text for debugging</H2>
<P>Our aim is to write a generic iterate method, but to test this method and 
help with debugging, we will write two helper procedures, and a Visitor to 
output a TPerObjAbs hierarchy as text.</P>
<P>The helper functions we want have a signature like this:</P><PRE>// Convert a TPerObjAbs hierarchy to a stringfunction 
tiPerObjAbsAsString( pVisited : TPerObjAbs ) : string ;        

// Show a TPerObjAbs hierarchy   
procedure tiShowPerObjAbs( pVisited : TPerObjAbs ) ; </PRE>
<P>We shall create a text output Visitor that will have an interface like 
this:</P><PRE>TVisPerObjToText = class( TVisitor )
private
  FStream: TStringStream;
protected
  function AcceptVisitor : boolean ; override ;
public
  constructor Create ; override ;
  destructor Destroy ; override;
  procedure Execute( pVisited : TVisited ) ; override ;
  property Stream : TStringStream read FStream ;
end ;</PRE>
<P>TVisPerObjToText has the usual AcceptVisitor and Execute methods that we have 
now come to expect to find in a TVisitor descendent, along with an owned 
TStringStream to write our data out to.</P>
<P>The key method is Execute and its implementation looks like this:</P><PRE>procedure TVisPerObjToText.Execute(pVisited: TVisited);
var
  i : integer ;
  lslProps : TStringList ;
  lsValue : string ;
  lsPropName : string ;
begin
  inherited Execute( pVisited ) ;
  if not AcceptVisitor then
    Exit ; //==&gt;
  // Write out the class name of the object we are visiting.
  FStream.WriteString( Visited.ClassName + #13 + #10 ) ;
  // Create a string list to hold a list of property names
  lslProps := TStringList.Create ;
  try
    // Populate the string list, lslProps with a list of published
    // property names read from pVisited. The properties should be
    // simple data types like string or integer only.
    // No class propeties at this stage.
    tiGetPropertyNames( TPersistent( pVisited ),
                        lslProps,
                        ctkSimple + [tkVariant, tkEnumeration] ) ;
    // Scan the list of property names and write out the values for each one
    for i := 0 to lslProps.Count - 1 do 
	begin
      // Get the property name from the list
      lsPropName := lslProps.Strings[i] ;
      // Get the property value (the third parameter means we want a string)
      lsValue := GetPropValue( pVisited, lsPropName, true ) ;
      lsValue := ' ' +
        lslProps.Strings[i] +
        ' = ' +
        lsValue ;
      // Write out the property name and value
      FStream.WriteString( lsValue + #13 + #10 ) ;
    end ;
  finally
    lslProps.Free ;
  end ;
end;</PRE>
<P>A note about GetPropValue and SetPropValue: </P>
<P>There are several things going on in this Execute method. Firstly we write 
out the class name of the object being visited. Next we create a TStringList to 
hold a list of published property names read from the class being visited. We 
then call tiGetPropertyNames to populate this list. tiGetPropertyNames is 
central to everything we shall do with iteration from now on and we will look at 
its implementation in just a second. Once we have a list of property names, we 
scan the list and write name = value pairs for each property and its 
corresponding value.</P>
<P>To create and execute this visitor we have the two helper functions 
tiPerObjAbsAsString and tiShowPerObjAbs. The implementation of 
tiPerObjAbsAsString looks like this:</P><PRE>function tiPerObjAbsAsString( pVisited : TPerObjAbs ) : string ;
var
  lVis : TVisPerObjToText ;
begin
  lVis := TVisPerObjToText.Create ;
  try
    pVisited.Iterate( lVis ) ;
    result := lVis.Stream.DataString ;
  finally
    lVis.Free ;
  end ;
end ;</PRE>
<P>An instance of TVisPerObjToText is created then passed to the iterate method 
of the object at the top of the class hierarchy we want to display. The visitor 
writes out its data to the TStringStream then we read this information and pass 
it back as the function’s result.</P>
<P>The second helper function tiShowPerObjAbs is used to call 
tiPerObjAbsAsString and display its value. The implementation of tiShowPerObjAbs 
looks like this:</P><PRE>procedure tiShowPerObjAbs( pVisited : TPerObjAbs ) ;
begin
  ShowMessage( tiPerObjAbsAsString( pVisited )) ;
end ;</PRE>
<P>The data we stored in an Interbase database in the previous section looks 
like this when it is displayed </P>
<P><IMG height=170 
src="tiOFP Documentation - Building an abstract BOM with the composite pattern_files/4_BuildingAnAbstractBOMWithTheComposite_clip_image002_0001.jpg" 
width=235> </P>
<P>The implementation of these two functions in the tiOPF uses the same 
concepts, but it indents values so it is easier to see which class owns what. 
The data is shown using a dialog with a scrolling text region (with a fixed with 
font) because some object hierarchies can be very big and overflow when 
displayed with ShowMessage.</P>
<H2>Getting a list of property names with tiGetPropertyNames</H2>
<P>The method tiGetPropertyNames is fundamental to the core of the tiOPF. If you 
are an interface zealot, and you are offended at the concept of using Delphi’s 
Run Time Type Information (RTTI) at the heart of the framework, the now is a 
good time to stop reading. From now on, we will be using tiGetPropertyNames 
everywhere. For example, RTTI is used for these purposes:</P>
<UL>
  <LI>At the lowest level of the hierarchy to control the iteration over owned 
  objects 
  <LI>In the TtiListView for reading what columns of data to display 
  <LI>In the TtiTreeView for reading nested objects and the data to display 
  <LI>In the TtiPerAware edit controls for linking a TPerObjAbs up to the 
  control 
  <LI>In the TPerObjAbs’s Assign and Clone methods which are used for copying 
  objects 
  <LI>In the class-to-database mapping framework which lets us read complex 
  hierarchies of objects without writing any SQL </LI></UL>
<P>There are other ways of achieving this than RTTI, however Delphi makes all 
this easy with RTTI so why not use it?</P>
<P>tiGetPropertyNames is overloaded and the two possible interfaces are shown 
below:</P><PRE><SPAN class=SourceCode>
procedure tiGetPropertyNames( pPersistent : TPersistent ;
   pSL : TStringList ;
   pPropFilter : TTypeKinds = ctkSimple ) ; overload ;
procedure tiGetPropertyNames( pPersistent : TPersistentClass ;
   pSL : TStringList ;
   pPropFilter : TTypeKinds = ctkSimple ) ; overload ;</PRE>
<P>Three parameters are passed: pPersistent which is either an instance of a 
TPersistent, or a TPersistent class type, a string list to hold the resulting 
property names and a property filter which contains a list of the property types 
to be read into the string list.</P><PRE>procedure tiGetPropertyNames( pPersistent : TPersistentClass ;
   pSL : TStringList ;
   pPropFilter : TTypeKinds = ctkSimple ) ;
var
  lCount : integer ;
  lSize : integer ;
  lList : PPropList ;
  i : integer ;
begin
  lCount := GetPropList(pPersistent.ClassInfo, pPropFilter, nil);
  lSize := lCount * SizeOf(Pointer);
  GetMem(lList, lSize);
  try
    GetPropList(pPersistent.ClassInfo, pPropFilter, lList);
    for i := 0 to lcount - 1 do
    psl.add( lList[i].Name ) ;
  finally
    FreeMem( lList, lSize ) ;
  end ;
   end ;</PRE>
<P>Now I am happy to confess that I don&amp;quo;t totally understand this code, 
except to say that GetPropList is declared in the TypInfo.pas unit. If you 
search the Delphi 5 VCL code you will find it called only once in the entire VCL 
in DsgnIntf.pas and its this code that I used to work out how to read a list of 
property names.</P><PRE>constructor TPropInfoList.Create(Instance: TPersistent; Filter: TTypeKinds);
begin
  FCount := GetPropList(Instance.ClassInfo, Filter, nil);
  FSize := FCount * SizeOf(Pointer);
  GetMem(FList, FSize);
  GetPropList(Instance.ClassInfo, Filter, FList);
end;</PRE>
<P>I don&amp;quo;t understand it, but it does work and works well.</P>
<P>The third parameter passed to tiGetPropertyNames is a set of TTypeKind. A 
TTypeKind can be any of the following:</P><PRE>TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
   tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
   tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);</PRE>
<P>In tiUtils.pas I have declared the following constants to make using 
tiGetPropertyNames easier:</P><PRE>const
  // Type kinds for use with tiGetPropertyNames
  // All string type properties
  ctkString = [ tkChar, tkString, tkWChar, tkLString, tkWString ] ;
  // Integer type properties
  ctkInt = [ tkInteger, tkInt64 ] ;
  // Float type properties
  ctkFloat = [ tkFloat ] ;
  // Numeric type properties
  ctkNumeric = [tkInteger, tkInt64, tkFloat];
  // All simple types (string, int, float)
  ctkSimple = ctkString + ctkInt + ctkFloat ;</PRE>

⌨️ 快捷键说明

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