diff --git a/ObservableTests.pas b/ObservableTests.pas index bd975e4..75d14d5 100644 --- a/ObservableTests.pas +++ b/ObservableTests.pas @@ -13,25 +13,64 @@ TObservableTests = class(TTestCase) procedure ObservableSetValueChangesValue; procedure DependentObservableReturnsValue; -// procedure DependentObservableEvaluatesOnlyOnceAfterChange; // not yet implemented + procedure DependentObservableEvaluatesOnlyOnceAfterChange; procedure DependentObservableUpdatesValueWhenDependencyChanges; + + procedure DependentObservableClearsOldDependencies; end; implementation +type + KO = Observable; + { TObservableTests } -//procedure TObservableTests.DependentObservableEvaluatesOnlyOnceAfterChange; -//var -// o: IObservable; -// count: Integer; -//begin -// count := 0; -// o := TDependentObservable.Create(function: string begin Inc(count); Result := 'test' end); -// CheckEquals('test', o.Value); -// CheckEquals('test', o.Value); -// CheckEquals(1, count); -//end; +procedure TObservableTests.DependentObservableClearsOldDependencies; +var + a: IObservable; + b, c: IObservable; + o: IObservable; + count: Integer; +begin + a := KO.Create(False); + b := KO.Create('true'); + c := KO.Create('false'); + o := KO.Computed( + function: string + begin + Inc(count); + if a.Value then + Result := b.Value + else + Result := c.Value + end); + count := 0; + CheckEquals('false', o.Value); + CheckEquals(0, count); + b.Value := 'TRUE'; + CheckEquals(0, count); + c.Value := 'FALSE'; + CheckEquals('FALSE', o.Value); + CheckEquals(1, count); + a.Value := True; + CheckEquals('TRUE', o.Value); + CheckEquals(2, count); + c.Value := 'false'; + CheckEquals(2, count); +end; + +procedure TObservableTests.DependentObservableEvaluatesOnlyOnceAfterChange; +var + o: IObservable; + count: Integer; +begin + count := 0; + o := TDependentObservable.Create(function: string begin Inc(count); Result := 'test' end); + CheckEquals('test', o.Value); + CheckEquals('test', o.Value); + CheckEquals(1, count); +end; procedure TObservableTests.DependentObservableReturnsValue; var diff --git a/SimpleMVVM.Observable.pas b/SimpleMVVM.Observable.pas index d5017b7..9cb859c 100644 --- a/SimpleMVVM.Observable.pas +++ b/SimpleMVVM.Observable.pas @@ -17,63 +17,80 @@ interface SysUtils; type + TAction = reference to procedure (const Arg1: T); + IObservable = interface(IInvokable) ['{3F78EF38-FA16-4E08-AD8D-3FD9A5E44BEF}'] + {$REGION 'Property Accessors'} function GetValue: TValue; procedure SetValue(const value: TValue); + {$ENDREGION} +// procedure Subscribe(const action: TAction); +// procedure Unsubscribe(const action: TAction); property Value: TValue read GetValue write SetValue; end; IObservable = interface(IInvokable) + {$REGION 'Property Accessors'} function GetValue: T; procedure SetValue(const value: T); + {$ENDREGION} +// procedure Subscribe(const action: TAction); +// procedure Unsubscribe(const action: TAction); property Value: T read GetValue write SetValue; end; - TAction = reference to procedure (const Arg1: T); - TObservableBase = class(TInterfacedObject, IObservable) private fDependencies: TList; - strict protected - class var ObservableStack: TStack; - constructor Create; - procedure Changed; virtual; + fSubscribers: TList; + {$REGION 'Property Accessors'} function GetValueNonGeneric: TValue; virtual; abstract; procedure SetValueNonGeneric(const value: TValue); virtual; abstract; function IObservable.GetValue = GetValueNonGeneric; procedure IObservable.SetValue = SetValueNonGeneric; + {$ENDREGION} + strict protected + class var ObservableStack: TStack; + constructor Create; + procedure ClearDependencies; procedure RegisterDependency; + procedure Notify; virtual; public class constructor Create; class destructor Destroy; - destructor Destroy; override; end; + TObservableStack = TStack; + TObservable = class(TObservableBase) private fGetter: TFunc; fSetter: TAction; - protected + {$REGION 'Property Accessors'} function GetValueNonGeneric: TValue; override; final; procedure SetValueNonGeneric(const value: TValue); override; final; + {$ENDREGION} public constructor Create(const getter: TFunc); overload; constructor Create(const getter: TFunc; const setter: TAction); overload; end; TDependentObservable = class(TObservableBase) - protected - fValue: TValue; + private fGetter: TFunc; fSetter: TAction; + fValue: TValue; fIsNotifying: Boolean; fNeedsEvaluation: Boolean; - procedure Changed; override; - procedure Evaluate; + {$REGION 'Property Accessors'} function GetValueNonGeneric: TValue; override; final; procedure SetValueNonGeneric(const value: TValue); override; final; + {$ENDREGION} + procedure Evaluate; + protected + procedure Notify; override; public constructor Create(const getter: TFunc); overload; constructor Create(const getter: TFunc; const setter: TAction); overload; @@ -84,11 +101,12 @@ TObservable = class(TObservableBase, IObservable) fValue: T; class var Comparer: IEqualityComparer; class constructor Create; - function GetValue: T; - procedure SetValue(const value: T); - protected + {$REGION 'Property Accessors'} function GetValueNonGeneric: TValue; override; final; procedure SetValueNonGeneric(const value: TValue); override; final; + function GetValue: T; + procedure SetValue(const value: T); + {$ENDREGION} public constructor Create; overload; constructor Create(const value: T); overload; @@ -96,28 +114,37 @@ TObservable = class(TObservableBase, IObservable) TDependentObservable = class(TObservableBase, IObservable) private - fValue: T; fGetter: TFunc; fSetter: TAction; + fValue: T; fIsNotifying: Boolean; fNeedsEvaluation: Boolean; - procedure Evaluate; + {$REGION 'Property Accessors'} + function GetValueNonGeneric: TValue; override; final; + procedure SetValueNonGeneric(const value: TValue); override; final; function GetValue: T; procedure SetValue(const value: T); + {$ENDREGION} + procedure Evaluate; protected - procedure Changed; override; - function GetValueNonGeneric: TValue; override; final; - procedure SetValueNonGeneric(const value: TValue); override; final; + procedure Notify; override; public constructor Create(const getter: TFunc); overload; constructor Create(const getter: TFunc; const setter: TAction); overload; end; -type TValueHelper = record helper for TValue function ToType: T; end; + Observable = record + class function Create: IObservable; overload; static; inline; + class function Create(const value: T): IObservable; overload; static; inline; + + class function Computed(const getter: TFunc): IObservable; overload; static; inline; + class function Computed(const getter: TFunc; const setter: TAction): IObservable; overload; static; inline; + end; + implementation uses @@ -138,6 +165,25 @@ function TValueHelper.ToType: T; end; +{$REGION 'TObservableStackHelper'} + +type + TObservableStackHelper = class helper for TObservableStack + public + function Peek: TObservableBase; + end; + +function TObservableStackHelper.Peek: TObservableBase; +begin + if Count = 0 then + Result := nil + else + Result := inherited Peek; +end; + +{$ENDREGION} + + {$REGION 'TObservableBase'} class constructor TObservableBase.Create; @@ -154,31 +200,44 @@ constructor TObservableBase.Create; begin inherited Create; fDependencies := TList.Create; + fSubscribers := TList.Create; end; destructor TObservableBase.Destroy; begin fDependencies.Free; + fSubscribers.Free; inherited; end; -procedure TObservableBase.Changed; +procedure TObservableBase.ClearDependencies; +var + dependency: TObservableBase; +begin + for dependency in fDependencies do + dependency.fSubscribers.Remove(Self); + fDependencies.Clear; +end; + +procedure TObservableBase.Notify; var observable: TObservableBase; begin - for observable in fDependencies do - observable.Changed; + for observable in fSubscribers do + observable.Notify; end; procedure TObservableBase.RegisterDependency; var - observable: TObservableBase; + frame: TObservableBase; begin - if ObservableStack.Count > 0 then + frame := ObservableStack.Peek; + if Assigned(frame) then begin - observable := ObservableStack.Peek; - if not fDependencies.Contains(observable) then - fDependencies.Add(observable); + if not fSubscribers.Contains(frame) then + fSubscribers.Add(frame); + if not frame.fDependencies.Contains(Self) then + frame.fDependencies.Add(Self); end; end; @@ -209,7 +268,7 @@ function TObservable.GetValueNonGeneric: TValue; procedure TObservable.SetValueNonGeneric(const value: TValue); begin fSetter(value); - Changed; + Notify; end; {$ENDREGION} @@ -232,7 +291,7 @@ constructor TDependentObservable.Create(const getter: TFunc; Evaluate; end; -procedure TDependentObservable.Changed; +procedure TDependentObservable.Notify; begin Evaluate; inherited; @@ -246,6 +305,7 @@ procedure TDependentObservable.Evaluate; ObservableStack.Push(Self); try +// ClearDependencies; fValue := fGetter; finally ObservableStack.Pop; @@ -266,7 +326,7 @@ procedure TDependentObservable.SetValueNonGeneric(const value: TValue); if fIsNotifying then Exit; if Assigned(fSetter) then fSetter(value); - inherited Changed; + inherited Notify; end; {$ENDREGION} @@ -306,7 +366,7 @@ procedure TObservable.SetValue(const value: T); if not Comparer.Equals(fValue, value) then begin fValue := value; - Changed; + Notify; end; end; @@ -335,7 +395,7 @@ constructor TDependentObservable.Create(const getter: TFunc; Evaluate; end; -procedure TDependentObservable.Changed; +procedure TDependentObservable.Notify; begin Evaluate; inherited; @@ -349,6 +409,7 @@ procedure TDependentObservable.Evaluate; ObservableStack.Push(Self); try + ClearDependencies; fValue := fGetter; finally ObservableStack.Pop; @@ -374,7 +435,7 @@ procedure TDependentObservable.SetValue(const value: T); if fIsNotifying then Exit; if Assigned(fSetter) then fSetter(value); - inherited Changed; + inherited Notify; end; procedure TDependentObservable.SetValueNonGeneric(const value: TValue); @@ -385,4 +446,30 @@ procedure TDependentObservable.SetValueNonGeneric(const value: TValue); {$ENDREGION} +{$REGION 'Observable'} + +class function Observable.Create: IObservable; +begin + Result := TObservable.Create; +end; + +class function Observable.Create(const value: T): IObservable; +begin + Result := TObservable.Create(value); +end; + +class function Observable.Computed(const getter: TFunc): IObservable; +begin + Result := TDependentObservable.Create(getter); +end; + +class function Observable.Computed(const getter: TFunc; + const setter: TAction): IObservable; +begin + Result := TDependentObservable.Create(getter, setter); +end; + +{$ENDREGION} + + end.