Delphi中的重复设置逻辑

9

对于类的每个setter,我都必须实现一些事件逻辑(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;

您能否提供一种不必为每个setter重复这些行的实现方法?

1
在基于事件的编程中,您经常会遇到的一般性问题是+1。 - Tobias Langner
2
你应该首先检查Value <> OldValue,这是VCL中通常使用的习惯用语。可以在方法开始时或OnChanging事件之后进行检查(取决于OnChanging是否获取var参数,即它是否可以更改新值)。 - mghie
4个回答

13

试试这个:

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;

实际上,那与我的解决方案相同,只是语法更好而已。 - Tobias Langner
1
不仅是更好看的语法,Tobias。而是有效的语法。 - Rob Kennedy
我打错了 - 我刚刚使用了类型而不是变量名。我纠正了我的例子,因为我手头没有编译器。正如你所看到的,这是一个有效的语法。思路保持不变。你通过地址(通过指针或使用var)传递。编译器也是这样做的。在二进制级别上,结果是相同的 - 只是语法更好,就像我之前说的那样。甚至使用起来也不容易。 - Tobias Langner

7

Delphi支持索引属性。多个属性可以共享单个getter或setter,通过一个序数索引进行区分:

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;

您可以结合Cobus的答案来实现以下效果:

procedure TBlock.SetWeight(Index: TWeightType; const Value: Double);
begin
  case Index of
    wtIn: SetField(FWeightIn, Value);
    wtOut: SetField(FWeightOut, Value);
  end;
end;

这可能会给你一些想法,让你能够通过索引引用字段,而不是为相关值拥有两个完全独立的字段。

+1 针对我差点回答但认为太长的答案。看来它仍然可以被简洁地描述 :-) - Cobus Kruger

0
如果过程/函数的参数相同,并且 begin 和 end 之间的代码也相同,则可以直接使用。
procedure SetWeightValue(const Value: Double);
var OldValue: Double;
begin
  OldValue := FWeightIn;
  DoOnChanging(OldValue, Value);
  FWeightIn := Value;
  DoOnChanged(OldValue, Value);
end;

就是这样...


0
你可以添加一个额外的方法。例如:
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;

我还没有编译/测试代码。这个想法是拥有一个通用的设置器方法,它可以使用指向位置的指针工作。专门的版本只需使用要设置的变量的地址调用通用方法。 但是,您必须为每种类型的变量(double、integer等)拥有一种类型的通用设置器方法。您可以修改它以便在指针和变量长度上工作,以适用于所有类型-这取决于您是否值得这样做。


你应该使用类似 Cobus Kruger 建议中的 var 参数,而不是指针。 - dummzeuch
1
@Tobias:不只是语法糖。使用var参数可以避免传递空指针,这样SetValue()方法就不需要检查(顺便说一句,你的代码没有检查!)。一个更难被误用的API显然更好。 - mghie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接