views:

217

answers:

4

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?

A: 

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.

Tobias Langner
Rather than a Pointer you should use a var parameter like in Cobus Kruger's suggestion.
dummzeuch
that's just syntactic sugar, although it's nicer to read.
Tobias Langner
@Tobias: No, not just syntactic sugar. With the var parameter it's impossible to pass a NULL pointer, so the SetValue() method doesn't have to check for that (BTW, your code doesn't!). An API with less potential for misuse is simply better.
mghie
korrekt, I missed that. Thx.
Tobias Langner
+11  A: 

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;
Cobus Kruger
actually - that's the same as my solution with a nicer syntax though.
Tobias Langner
Not just *nicer* syntax, Tobias. *Valid* syntax.
Rob Kennedy
I mistyped - I just used the Type instead of the variable name. I corrected my example I had no compiler at hand. As you can see it is a valid syntax. The idea stays the same. You pass the adress (via pointer or using var). The compiler does the same. On a binary level the result is the same - just the syntax is nicer as I said before. It's not even easier to use.
Tobias Langner
+6  A: 

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.

Rob Kennedy
+1 very interesting! I wasn't aware of this.
Smasher
+1 for the answer I almost gave but thought it would be too long. Seems it can still be described compactly after all :-)
Cobus Kruger
A: 

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...

delphigeist