Repeated setters logic in Delphi
-
19-09-2019 - |
Question
For each setter of a class I have to implement some event logic (OnChanging, OnChanged):
procedure TBlock.SetWeightIn(const Value: Double);
var OldValue: Double;
begin
OldValue := FWeightIn;
DoOnChanging(OldValue, Value);
FWeightIn := Value;
DoOnChanged(OldValue, Value);
end;
procedure TBlock.SetWeightOut(const Value: Double);
var OldValue: Double;
begin
OldValue := FWeightOut;
DoOnChanging(OldValue, Value);
FWeightOut := Value;
DoOnChanged(OldValue, Value);
end;
Can you please suggest a way to implement this without duplicating all these lines for each setter?
Solution
Try this:
procedure TBlock.SetField(var Field: Double; const Value: Double);
var
OldValue: Double;
begin
OldValue := Field;
DoOnChanging(OldValue, Value);
Field := Value;
DoOnChanged(OldValue, Value);
end;
procedure TBlock.SetWeightIn(const Value: Double);
begin
SetField(FWeightIn, Value);
end;
procedure TBlock.SetWeightOut(const Value: Double);
begin
SetField(FWeightOut, Value);
end;
OTHER TIPS
Delphi supports indexed properties. Multiple properties can share a single getter or setter, differentiated by an ordinal index:
type
TWeightType = (wtIn, wtOut);
TBlock = class
private
procedure SetWeight(Index: TWeightType; const Value: Double);
function GetWeight(Index: TWeightType): Double;
public
property InWeight: Double index wtIn read GetWeight write SetWeight;
property OutWeight: Double index wtOut read GetWeight write SetWeight;
end;
You can combine this with Cobus's answer to get this:
procedure TBlock.SetWeight(Index: TWeightType; const Value: Double);
begin
case Index of
wtIn: SetField(FWeightIn, Value);
wtOut: SetField(FWeightOut, Value);
end;
end;
This might give you ideas for other ways you can refer to your fields by index instead of having two completely separate fields for such related values.
You could add an additional method. Something like:
procedure TBlock.setValue(const Value : Double; Location : PDouble);
var
OldValue : Double;
begin
OldValue:=Location^;
DoOnChanging(OldValue,Value);
Location^:=Value;
DOnChanged(OldValue, Value);
end;
procedure TBlock.setWeightOut(const Value : Double);
begin
setValue(value, @FWeightOut);
end;
I haven't compiled / tested the code though. The idea is to have a general setter method that works with a pointer to the location. The specialized versions just call the gerneral method with the adress of the variable to be set. You have to have one type of general setter method per type of variable (double, integer, ...) though. You could modify it to work on Pointer and length of a variable to work with all types - your decision if it's worth it.
if procedure/function's parameters are same and code between begin and end are same then you can just use
procedure SetWeightValue(const Value: Double);
var OldValue: Double;
begin
OldValue := FWeightIn;
DoOnChanging(OldValue, Value);
FWeightIn := Value;
DoOnChanged(OldValue, Value);
end;
That's it...