Author: Tomas Rutkauskas
I have a unit that all it does is store SQL statement for me to load and right now
I'm doing:
1 if ReportName = "Some_Report_Name" then
2 LoadSomeReportNameSql;
3 else if ReportName = "Some_Other_Report" then
4 LoadSomeOtherReportSql;
I have about 200 reports so far...would a case statment be faster? I would, of
course, change the identifier for the report to a numeric identifier, rather than a
string identifier. My concern is that there will begin to be a very noticable
difference once I get up to the 500 or so reports.
Answer:
With that many reports, there are two better solutions than an if/ then or case
statement.
Solve 1:
An array of records containing the report name and report procedure might be faster
and easier to maintain. The list could be sorted on the report name, and a binary
search algorithm could be used to quickly locate the correct report procedure to
execute.
This method is not new, but works very well. It is not automagical, so the
programmer has to do some typing. It could be improved in a myriad of ways, like
array of const parameters, TVarRec results, action identifers and encapsulation in
a class. The last could get hairy if you expect that class to serve objects of
other classes as well, but it is possible.
5 unit NamedFunctions;
6
7 interface
8
9 const
10 MaxFuncs = 3;
11 MaxFuncName = 13;
12
13 type
14 TFuncRange = 1..MaxFuncs;
15 TNamedFunc = function(args: string): string;
16 TFuncName = string[MaxFuncName];
17 TFuncInfo = record
18 Name: TFuncName;
19 Func: TNamedFunc
20 end; { TNamedFunc }
21
22 TFuncList = array[TFuncRange] of TFuncInfo;
23 function XSqrt(args: string): string;
24 function XUpStr(args: string): string;
25 function XToggle(args: string): string;
26
27 const
28 {This list must be sorted for the function to be found}
29 FuncList: TFuncList = ((Name: 'xsqrt'; Func: XSqrt), (Name: 'xtoggle'; Func:
30 XToggle), (Name: 'xupstr'; Func: XUpStr));
31
32 function ExecFunc(AName: TFuncName; args: string): string;
33
34 implementation
35
36 uses
37 Dialogs, SysUtils;
38
39 function ExecFunc(AName: TFuncName; args: string): string;
40 { Binary search is overkill for a small number of functions. }
41 var
42 CompRes, i, j, m: integer;
43 Found: boolean;
44 begin
45 AName := LowerCase(AName);
46 i := 1;
47 j := MaxFuncs;
48 m := (i + j) shr 1;
49 Found := false;
50 while not Found and (i <= j) do
51 begin
52 CompRes := AnsiCompareStr(AName, FuncList[m].Name);
53 if CompRes < 0 then
54 j := m - 1
55 else if CompRes > 0 then
56 i := m + 1
57 else
58 Found := true;
59 if not Found then
60 m := (i + j) shr 1
61 end;
62 if Found then
63 Result := FuncList[m].Func(args)
64 else
65 begin
66 Result := '';
67 ShowMessage('Function ' + AName + ' not found in list')
68 end;
69 end;
70
71 function XSqrt(args: string): string;
72 var
73 value: real;
74 begin
75 value := 0;
76 try
77 value := StrToFloat(args)
78 except
79 on EConvertError do
80 ShowMessage(args + ' is not a valid real number (XStr)')
81 end;
82 if value >= 0 then
83 Result := FloatToStr(sqrt(value))
84 else
85 begin
86 Result := '0.0';
87 ShowMessage('Negative number passed to XSqrt')
88 end;
89 end;
90
91 function XUpStr(args: string): string;
92 begin
93 Result := UpperCase(args)
94 end;
95
96 function XToggle(args: string): string;
97 { Anything other than 'TRUE' or 'T' is assumed false. }
98 begin
99 args := UpperCase(args);
100 if (args = 'TRUE') or ((length(args) = 1) and (args = 'T')) then
101 Result := 'FALSE'
102 else
103 Result := 'TRUE'
104 end;
105
106 end.
Solve 2:
Another way to go would be to use the GetProcAddress Win32 API function to locate
the report procedure based on the report name. This way you could store the report
names and report procedure names in a text file or database. (Tip: EXEs can export
routines just like DLLs can. GetProcAddress only finds exported routine names). The
code might look something like this (off the top of my head...):
107 unit MyReports;
108
109 interface
110
111 type
112 TReportProcedure = procedure;
113
114 procedure LoadSomeReportNameSql;
115 procedure LoadSomeOtherReportSql;
116 procedure ExecuteReport(AReportName: string);
117
118 implementation
119
120 procedure ExecuteReport(AReportName: string);
121 var
122 ReportProc: TReportProcedure;
123 ProcPointer: TFarProc;
124 begin
125 {Table contains two columns: "Report Name" and "Report Procedure". Primary key
126 is "Report Name"}
127 try
128 Table1.Open;
129 if Table1.FindKey([AReportName]) then
130 begin
131 {Get the address of the exported report procedure}
132 ProcPointer := GetProcAddress(HInstance, Table1.FieldByName('Report
133 Procedure');
134 if Assigned(ProcPointer) then
135 begin
136 ReportProcedure := TReportProcedure(ProcPointer);
137 ReportProcedure;
138 end;
139 end;
140 finally
141 Table1.Close;
142 end;
143 end;
144
145 procedure LoadSomeReportNameSql;
146 begin
147 end;
148
149 procedure LoadSomeOtherReportSql;
150 begin
151 end;
152
153 exports
154 LoadSomeReportNameSql;
155 LoadSomeOtherReportSql;
156
157 end.
|