- Delphi Cookbook
- Daniele Spinetti Daniele Teti
- 551字
- 2025-04-04 16:22:47
How to do it...
The sample customization is simple; however, the difficulty begins when you have to handle which customization you have to choose from among those available. Sure, you can define some sort of parameters; but your code will get a lot of if just to understand which calculation to apply. And, even worse, a change in one of your criteria could break something in another. Bad approach! We can configure our software without if statements using RTTI. In this recipe, all the calculus engines are implemented in four different classes in four different units. You can also define all the criteria in only one unit, but it is not mandatory.
In the following table, we see a summary of the customers and the customizations implemented:
Customer Unit/class name Calculation criteria
Default
(no customization)
CalculationCustomerDefaultU
TCalculationCustomerDefault
Result := (Price * Quantity) * (1 - Discount / 100);
City Mall CalculationCustomer_CityMall
TCalculationCustomer_CityMall
Result := (Price * Quantity) * (1 - Discount / 100);
if Result > 1000 then
Result := Result * 0.90; Country Road Shop
CalculationCustomer_CountryRoad
TCalculationCustomer_CountryRoad
if Quantity > 10 then
if Discount < 50 then
Discount := 50;
Result := (Price * Quantity) * (1 - Disco
unt / 100); Spark Industries
CalculationCustomer_Spark
TCalculationCustomer_Spark
Result := (Price * Quantity) * (1 - Discount / 100);
if DayOfTheWeek(Date) in [1, 7] then
Result :=Result * 0.50;
When the program starts, it looks for a configuration file. In the first line of the file, there is a fully qualified class name (UnitName.ClassName) that implements the needed calculus criteria. That string is used to create the related class, and the instance will be used to calculate the total price when needed. The interesting code is as follows:
procedure TMainForm.LoadCalculationEngine; var TheClassName: string; CalcEngineType: TRttiType; const CONFIG_FILENAME = '..\..\calculation.config.txt'; begin if not TFile.Exists(CONFIG_FILENAME) then TheClassName := 'CalculationCustomerDefaultU.' + 'TCalculationCustomerDefault' else TheClassName := TFile.ReadAllLines(CONFIG_FILENAME)[0]; CalcEngineType := FCTX.FindType(TheClassName.Trim); if not assigned(CalcEngineType) then raise Exception.CreateFmt('Class %s not found', [TheClassName]); if not CalcEngineType.GetMethod('Create').IsConstructor then raise Exception.CreateFmt('Cannot find Create in %s', [TheClassName]); FCalcEngineObj := CalcEngineType.GetMethod('Create') .Invoke(CalcEngineType.AsInstance.MetaclassType, []).AsObject; FCalcEngineMethod := CalcEngineType.GetMethod('GetTotal'); Label5.Caption := 'Current Calc Engine: ' + TheClassName; end;
FCalcEngineObj is a TObject reference that holds your actual calculation engine, while FCalcEngineMethod is an RTTI object that keeps a reference to the method to call when the calculus is needed.
Now, in the dataset OnCalcFields event handler, there is this code:
procedure TMainForm.ClientDataSet1CalcFields(DataSet: TDataSet); begin ClientDataSet1TOTAL.Value := FCalcEngineMethod.Invoke(FCalcEngineObj, [ClientDataSet1PRICE.Value, ClientDataSet1QUANTITY.Value, ClientDataSet1DISCOUNT.Value]).AsCurrency; end;
Run the program and check which calculus engine is loaded. Then stop the program, open the configuration file, and write another QualifiedClassName (unit name plus class name), choosing from all those available. Run the program. As you can see, the correct engine is selected and the customization is applied without changing the working code.
On writing the CalculationCustomer_CityMall.TCalculationCustomer_CityMall class in the file, you will get the following behavior:
