2012-03-30 359 views
3

我想監督一個類的實例。每當該對象的屬性發生變化時,我希望能夠檢查該對象,而無需自己實現該功能。特別是如果班級有很多屬性。監督對象屬性值的更改

我有一類這樣的:

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 

是有一個標準的方法來做到這一點?

+0

是什麼你要求還不完全清楚。你是否正在尋找一種方法來檢測對象的屬性何時被修改而不需要爲每個屬性的getter觸發事件? – 2012-03-30 17:21:48

+1

請注意,你的例子並不清楚,因爲'changed'是一個時態屬性:第二個'c.changed'後應該返回什麼?假如只有一個客戶檢查變化或者它是否保持真實,它是否應該重置爲假?另外,你是否願意改變'TMyClass',或者你想從外部感知變化(這是不可能的)? – jpfollenius 2012-03-30 17:25:46

+0

請確定您的Delphi版本(添加適當的特定標籤)! – menjaraz 2012-03-31 08:45:32

回答

1

有兩種方法我知道這樣做,但都不整齊。如果我們有一個OnProperyChanged事件會很好,但是我們不這樣做,所以你必須自己做一些事情。選項有:

  1. 在屬性設置程序中爲每個屬性設置一個CHANGED布爾值。

  2. 使用RTTI保留所有屬性數據的影子副本,並與定時器上的副本進行比較以設置CHANGED標誌(如果不同)。

我很想知道更好的方法。

+0

虛擬代理功能是另一種方式(請參閱我的答案) – whosrdaddy 2012-04-02 12:09:59

4

布賴恩放入選項#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; 
2

我做了關於這個問題的一些研究,並與TAspectWeaver demoDSharp 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爲此工作。

我喜歡沃倫的回答雖然(魔少),我只是想證明它是合法的(虛擬函數代理)