监督对象属性值的更改
我想监督一个类的实例。每当该对象的属性发生变化时,我希望能够检查该对象,而无需自己实现该功能。特别是如果班级有很多属性。监督对象属性值的更改
我有一类这样的:
TMyClass = class
private
FTest1: Integer;
...
FTestN: Integer;
public
property Test1: Integer read FTest1 write FTest1;
...
property TestN: Integer read FTest1 write FTest1;
end.
当使用这个类:
c := TMyClass.Create;
这将是真棒有类似:
c.changed // -> false
c.Test1 := 1;
c.changed // -> true
是有一个标准的方法来做到这一点?
有两种方法我知道这样做,但都不整齐。如果我们有一个OnProperyChanged事件会很好,但是我们不这样做,所以你必须自己做一些事情。选项有:
在属性设置程序中为每个属性设置一个CHANGED布尔值。
使用RTTI保留所有属性数据的影子副本,并与定时器上的副本进行比较以设置CHANGED标志(如果不同)。
我很想知道更好的方法。
虚拟代理功能是另一种方式(请参阅我的答案) – whosrdaddy 2012-04-02 12:09:59
布赖恩放入选项#1时,使用的典型模式是属性中的setter方法。我想给你写一些示例代码,这样你就可以看到人们在做什么。
请注意,NameChanged是一个虚拟方法,因为我可能想要声明基类TPersonInfo,然后为TJanitorInfo创建子类,并且TJanitorInfo可能为NameChanged
执行更复杂的实现。因此,处理属性值更改的一个级别是子类可以覆盖方法。但对于不是子类的东西,你在你的问题中建议将布尔标志设置为true。那就需要“重复检查该标志”(称为轮询)。这最终可能会比它的价值更多的工作。也许你需要的是以下所示的“事件”,也称为“回调”或“指向方法的指针”。在delphi中,这些属性以字On
开头。 OnNameChanged
就是这样一个事件。
type
TPersonInfo = class
private
FName:String;
FOnChangedProperty:TNotifyEvent;
protected
procedure SetName(aName:String);
procedure NameChanged; virtual;
published
property Name:String read fName write SetName;
property OnChangedProperty:TNotifyEvent read FOnChangedProperty write FOnChangedProperty;
end;
...
implementation
procedure TPersonInfo.SetName(aName:String);
begin
if aName<>FName then begin
aName := FName;
NameChanged;
end;
end;
procedure NameChanged; virtual;
begin
// option A: set a boolean flag. Exercise for reader: When does this turn off?
FNameChanged := true;
// option B: refresh visual control because a property changed:
Refresh;
// option C: something else (math or logic) might need to be notified
if Assigned(FOnChangedProperty) then
FOnChangedProperty(Self);
end;
我做了关于这个问题的一些研究,并与TAspectWeaver
demo从DSharp project发挥来实现这一目标:
unit Aspects.ChangeDetection;
interface
uses
DSharp.Aspects,
Rtti,
SysUtils,
StrUtils;
type
TChangeDetectionAspect = class(TAspect)
private
class var IsChanged : Boolean;
public
class procedure DoAfter(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; var Result: TValue); override;
class procedure DoBefore(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out DoInvoke: Boolean;
out Result: TValue); override;
class procedure DoException(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out RaiseException: Boolean;
Exception: Exception; out Result: TValue); override;
end;
ChangeDetectionAttribute = class(AspectAttribute)
public
constructor Create;
end;
[ChangeDetection]
IChangeable = interface
['{59992EB4-62EB-4A9A-8216-1B14393B003B}']
function GetChanged: Boolean;
procedure SetChanged(const Value: Boolean);
property Changed : boolean read GetChanged write SetChanged;
end;
TChangeable = class(TInterfacedObject, IChangeable)
private
FChanged : Boolean;
function GetChanged: Boolean;
procedure SetChanged(const Value: Boolean);
public
property Changed : boolean read GetChanged write SetChanged;
end;
implementation
{ TChangeDetectionAspect }
class procedure TChangeDetectionAspect.DoAfter(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; var Result: TValue);
var ic : IChangeable;
begin
if Supports(Instance, IChangeable, ic) then
ic.Changed := IsChanged;
end;
class procedure TChangeDetectionAspect.DoBefore(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue);
var ctx : TRttiContext;
typ : TRttiType;
meth : TRttiMethod;
Res : TValue;
begin
IsChanged := False;
if StartsText('set', Method.Name) then
begin
ctx := TRttiContext.Create;
typ := ctx.GetType(Instance.ClassType);
// call Getxxx counterpart
meth := typ.GetMethod('G'+ Copy(Method.Name, 2, Maxint));
if Assigned(meth) then
try
Res := meth.Invoke(Instance, []);
IsChanged := Res.AsVariant <> Args[0].AsVariant;
except
end;
end;
end;
class procedure TChangeDetectionAspect.DoException(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out RaiseException: Boolean; Exception: Exception;
out Result: TValue);
begin
end;
{ ChangeDetectionAttribute }
constructor ChangeDetectionAttribute.Create;
begin
inherited Create(TChangeDetectionAspect);
end;
{ TChangeable }
function TChangeable.GetChanged: Boolean;
begin
Result := FChanged;
end;
procedure TChangeable.SetChanged(const Value: Boolean);
begin
FChanged := Value;
end;
end.
用法:
unit u_frm_main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Aspects.ChangeDetection, DSharp.Aspects.Weaver;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
IMyObject = interface(IChangeable)
function GetName: String;
procedure SetName(const Value: String);
property Name : String read GetName write SetName;
end;
TMyObject = class(TChangeable, IMyObject)
private
FName : String;
public
function GetName: String;
procedure SetName(const Value: String); virtual;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TMyObject }
function TMyObject.GetName: String;
begin
Result := FName;
end;
procedure TMyObject.SetName(const Value: String);
begin
FName := Value;
end;
procedure TForm1.FormCreate(Sender: TObject);
var MyObject : IMyObject;
begin
MyObject := TMyObject.Create;
MyObject.Changed := False;
AspectWeaver.AddAspect(TMyObject, TChangeDetectionAspect, '^Set');
MyObject.Name := 'yee';
if MyObject.Changed then
ShowMessage('yep changed');
MyObject.Name := 'yee';
if MyObject.Changed then
ShowMessage('oops, not changed should not display');
MyObject.Name := 'yeea';
if MyObject.Changed then
ShowMessage('yep changed');
end;
end.
请注意,你应该有至少Delphi2010为此工作。
我喜欢沃伦的回答虽然(魔少),我只是想证明它是合法的(虚拟函数代理)
是什么你要求还不完全清楚。你是否正在寻找一种方法来检测对象的属性何时被修改而不需要为每个属性的getter触发事件? – 2012-03-30 17:21:48
请注意,你的例子并不清楚,因为'changed'是一个时态属性:第二个'c.changed'后应该返回什么?假如只有一个客户检查变化或者它是否保持真实,它是否应该重置为假?另外,你是否愿意改变'TMyClass',或者你想从外部感知变化(这是不可能的)? – jpfollenius 2012-03-30 17:25:46
请确定您的Delphi版本(添加适当的特定标签)! – menjaraz 2012-03-31 08:45:32