2012-03-30 2 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? –

+1

Обратите внимание, что ваш пример не ясен, так как 'changed' является временным свойством: что он должен возвращать после второго' c.changed'? Должен ли он быть сброшен на false, предполагая, что только один клиент проверяет наличие изменений или должен ли он оставаться истинным? Кроме того, готовы ли вы изменить «TMyClass» или вы хотите ощущать изменения снаружи (что невозможно)? – jpfollenius

+0

Пожалуйста, уточните версию Delphi (добавьте соответствующий конкретный тег)! – menjaraz

ответ

1

Есть два пути, которые я знаю об этом, и они не являются аккуратными. Было бы здорово, если бы у нас было событие OnProperyChanged, но мы этого не делаем, поэтому вам нужно что-то сделать самостоятельно. Возможные варианты:

  1. Установите CHANGED Boolean внутри процедуры настройки свойств для каждого из ваших свойств.

  2. Используйте RTTI, чтобы сохранить теневую копию всех ваших данных о свойствах и сравнить с копией на таймере, чтобы установить флаг CHANGED, если он отличается.

Мне было бы очень интересно узнать лучший способ.

+0

виртуальная функция прокси - это еще один способ (см. Мой ответ) – whosrdaddy

4

Типичный шаблон, используемый в качестве метода сеттера в свойстве, так как Брайан помещает в опцию # 1. Мне хотелось написать вам образец кода, чтобы вы могли видеть, что делают люди.

Обратите внимание, что 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 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 для этого.

Я предпочитаю ответ Уоррен, хотя (меньше магии), я просто хотел показать, что это возможно (с прокси виртуальных функций)

Смежные вопросы