📄 tiofp documentation - building an abstract bom with the composite pattern.htm
字号:
( 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 ; //==>
// 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&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&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 + -