Some of my code use Date and Now. So the result for tests on that code is different depending on when it execute. That is of course bad.
So what is the recommended way of fake Date and Time in tests ?
Using interface ?
In order to make things testable you need to have or create seams in your code.
You might want to use the Ambient Context design pattern to achieve this in this particular example. While this looks like a singleton at first it actually allows temporarily replacing the used instance or the returned data. The following is an example how to implement it in Delphi based on this blog post.
I left out the thread safety and all that stuff to give you the basic idea how it works. You might also get rid of TDateTimeProvider singleton/class altogether and just make your own Now function that works with mocking.
program AmbientContextDemo;
uses
Generics.Collections,
TestFramework,
TestInsight.DUnit,
System.SysUtils;
type
TDateTimeProvider = class
strict private
class var fInstance: TDateTimeProvider;
class function GetInstance: TDateTimeProvider; static;
public
function Now: TDateTime;
class property Instance: TDateTimeProvider read GetInstance;
end;
TDateTimeProviderContext = class
strict private
var fContextNow: TDateTime;
class var contextStack: TStack<TDateTimeProviderContext>;
class function GetCurrent: TDateTimeProviderContext; static;
public
class constructor Create;
class destructor Destroy;
constructor Create(const contextNow: TDateTime);
destructor Destroy; override;
property ContextNow: TDateTime read fContextNow;
class property Current: TDateTimeProviderContext read GetCurrent;
end;
TStuff = class
// class procedure for demo purpose, no instance necessary in test
class function DoSomeDateTimeStuff_UsingNow: TDateTime;
class function DoSomeDateTimeStuff_UsingAmbientContext: TDateTime;
end;
TMyTest = class(TTestCase)
published
procedure TestIt_Fail;
procedure TestIt_Pass;
end;
{ TDateTimeProvider }
class function TDateTimeProvider.GetInstance: TDateTimeProvider;
begin
if not Assigned(fInstance) then
fInstance := TDateTimeProvider.Create;
Result := fInstance;
end;
function TDateTimeProvider.Now: TDateTime;
var
context: TDateTimeProviderContext;
begin
context := TDateTimeProviderContext.Current;
if Assigned(context) then
Result := context.ContextNow
else
Result := System.SysUtils.Now;
end;
{ TMyTest }
procedure TMyTest.TestIt_Fail;
begin
CheckEquals(EncodeDate(2018, 11, 11), TStuff.DoSomeDateTimeStuff_UsingNow);
end;
procedure TMyTest.TestIt_Pass;
var
ctx: TDateTimeProviderContext;
begin
ctx := TDateTimeProviderContext.Create(EncodeDate(2018, 11, 11));
try
CheckEquals(EncodeDate(2018, 11, 11), TStuff.DoSomeDateTimeStuff_UsingAmbientContext);
finally
ctx.Free;
end;
end;
{ TStuff }
class function TStuff.DoSomeDateTimeStuff_UsingNow: TDateTime;
begin
Result := Now; // using Now which is unmockable, only via hooking but that affects all occurences even in the RTL
end;
class function TStuff.DoSomeDateTimeStuff_UsingAmbientContext: TDateTime;
begin
Result := TDateTimeProvider.Instance.Now;
end;
{ TDateTimeProviderContext }
class constructor TDateTimeProviderContext.Create;
begin
contextStack := TStack<TDateTimeProviderContext>.Create;
end;
class destructor TDateTimeProviderContext.Destroy;
begin
contextStack.Free;
end;
constructor TDateTimeProviderContext.Create(const contextNow: TDateTime);
begin
fContextNow := contextNow;
contextStack.Push(Self);
end;
destructor TDateTimeProviderContext.Destroy;
begin
contextStack.Pop;
end;
class function TDateTimeProviderContext.GetCurrent: TDateTimeProviderContext;
begin
if contextStack.Count = 0 then
Result := nil
else
Result := contextStack.Peek;
end;
begin
RegisterTest(TMyTest.Suite);
RunRegisteredTests;
end.
It's mentioned above, but some example code may help. Here's a method I've been using for 20 years. Implement methods to replace Date and Now functions with your own that allow an override, then replace all references in your code to utilize the replacements. When you need to test, simply set TestDate to a value appropriate for your tests.
var
TestDate: TDateTime = 0;
function CurrDate: TDateTime;
begin
if TestDate = 0 then
Result := SysUtils.Date
else
Result := TestDate;
end;
function CurrDateTime: TDateTime;
begin
if (TestDate = 0) then
Result := Now
else
Result := TestDate + Time;
end;
Consider you call Date() and Now() without unit prefix like SysUtils.Date(). Then add MyTestUtils.pas unit to the end of uses with conditional compiling
{$IFDEF TestMode}
, MyTestUtils
{$ENDIF}
In MyTestUtils.pas you can define your own Date() and Now() functions that will be used instead of SysUtils ones.
However it's a "nasty hack" that normally doesn't work when a good programmer uses unit prefixes to call functions.
Related
I must first admit that I am from the .Net world and am currently relearning Delphi (XE 10.x) (from back in high school - MANY years ago). In .Net, the mediator pattern is fairly well handled by libraries such as MediatR or MassTransit. Yet, I have found very few libraries that support a dynamic (or semi-dynamic) implementation of the Mediator Pattern in Delphi. Without going to the fancy level of scanning the executing Rtti information, I wanted to create a simple mediator where I could register a CommandHandler by Request and then get a response back. Is this possible?
Here is some example code that I've made so far - but I'm just getting stuck on how to dynamically create the objects and whether my approach is even sound.
Before examining the code, I am not stuck on using a TDictionary<string, string> for registering the types, however, my limited knowledge of Rtti makes it difficult to figure out whether it should be using TClass or TRttiTypes. If either of those would be helpful, I would appreciate additional assistance on that.
// interface
uses
System.Generics.Collections;
type
TUnit = record
end;
IRequest<TResponse> = interface
end;
IRequest = interface(IRequest<TUnit>)
end;
IRequestHandler<TResponse; TRequest: IRequest<IResponse>> = interface(IInvokable)
function Handle(ARequest: TRequest): TResponse;
end;
IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
end;
TMediator = class
private
FRequestHandlers: TDictionary<string, string>;
public
constructor Create;
destructor Destroy; override;
procedure RegisterHandler(AHandlerClass, ARequestClass: TClass);
function Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
end;
// implementation
constructor TMediator.Create;
begin
Self.FRequestHandlers := TDictionary<string, string>.Create;
end;
destructor TMediator.Destroy;
begin
Self.FRequestHandlers.Free;
inherited;
end;
procedure TMediator.RegisterHandler(AHandlerClass, ARequestClass: TClass);
var
LTempRequestClass : string;
rContext : TRttiContext;
rType : TRttiType;
begin
if Self.FRequestHandlers.TryGetValue(ARequestClass.QualifiedClassName, LTempRequestClass) then
exit;
{ I would like to add some error checking functionality to prevent classes
that do not implement IRequest or IRequest<> from being added here. }
Self.FRequestHandlers.Add(ARequestClass.QualifiedClassName, AHandlerClass.QualifiedClassName);
end;
function TMediator.Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
var
LRequestHandlerClassName: string;
LRequestHandler : IRequestHandler<TResponse, TRequest>;
begin
if not Self.FRequestHandlers.TryGetValue(ARequest.QualifiedClassName, LRequestHandlerClassName) then
raise Exception.Create('Handler class not registered with this mediator.');
{ Not sure what to do here to get the LRequestHandler - I'm also using Spring4d,
so I considered using the QualifiedClassName as a way to resolve classes
registered in the TContainer }
Result := LRequestHandler.Handle(ARequest);
end;
My anticipated usage of this would be:
NOTE: Edits below - I want to be able to register and call ANY commands that implement IRequest or IRequest<> from a single moderator.
// interface
type
TMyResponse = class
private
FFoo: string;
public
property Foo: string read FFoo write FFoo;
end;
TMyResponse2 = class
private
FFoo2: string;
public
property Foo2: string read FFoo2 write FFoo2;
end;
TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
private
FBar: string;
public
property Bar: string read FBar write FBar;
end;
TMyRequest2 = class(TInterfacedObject, IRequest<TMyResponse2>)
private
FBar2: string;
public
property Bar2: string read FBar2 write FBar2;
end;
TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
public
function Handle(ARequest: TMyRequest): TMyResponse;
end;
TMyRequestHandler2 = class(TInterfacedObject, IRequestHandler<TMyResponse2, TMyRequest2>)
public
function Handle(ARequest: TMyRequest2): TMyResponse2;
end;
// implementation
var
AMediator: TMediator;
ARequest: TMyRequest;
ARequest2: TMyRequest2;
AResponse: TMyResponse;
AResponse2: TMyResponse2;
begin
AMediator := TMediator.Create;
ARequest := TMyRequest.Create;
ARequest2 := TMyRequest2.Create;
try
ARequest.Bar := 'something';
ARequest2.Bar2 := 'else';
// Not sure how I would get these either - seems best to use the qualified class name
AMediator.Register(TMyRequestHandler.QualifiedClassName, TMyRequest.QualifiedClassName);
AMediator.Register(TMyRequestHandler2.QualifiedClassName, TMyRequest2.QualifiedClassName);
AResponse := AMediator.Send(ARequest);
AResponse2 := AMediator.Send(ARequest2);
// Do something with this value
finally
AResponse2.Free;
AResponse.Free;
ARequest2.Free;
ARequest.Free;
AMediator.Free;
end;
end.
So, it seems I was going about this the wrong way, thanks to J... who made me rethink what I was doing. In summary, I was trying to have something act as a layer of dependency injection to be able to dynamically run a "Handler" based on a given "Request". In the end, it appears that the simple solution was to call the Spring4d DI layer I was already using to perform the function. I still feel like there is some fairly tight coupling, but I am currently satisfied with the result. Here is the code:
CQRSU.pas
unit CQRSU;
interface
uses
System.Generics.Collections,
Spring.Container;
type
TUnit = record
end;
IBaseRequest = interface(IInvokable)
['GUID']
end;
IRequest<TResponse> = interface(IBaseRequest)
['GUID']
end;
IRequest = interface(IRequest<TUnit>)
['GUID']
end;
IRequestHandler<TResponse; TRequest: IRequest<TResponse>> = interface(IInvokable)
['GUID']
function Handle(ARequest: TRequest): TResponse;
end;
IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
['GUID']
end;
implementation
end.
ServicesU.pas
unit ServicesU;
interface
uses
CQRSU;
type
TMyResponse = class
private
FMyResult: string;
public
property MyResult: string read FMyResult write FMyResult;
end;
TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
private
FMyParameter: string;
public
property MyParameter: string read FMyParameter write FMyParameter;
end;
TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
public
function Handle(ARequest: TMyRequest): TMyResponse;
end;
implementation
{ TMyRequestHandler }
function TMyRequestHandler.Handle(ARequest: TMyRequest): TMyResponse;
begin
Result := TMyResponse.Create;
Result.MyResult := ARequest.MyParameter + ' Processed';
end;
end.
TestCQRS.dpr
program TestCQRS;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Spring.Container,
System.SysUtils,
CQRSU in 'CQRSU.pas',
ServicesU in 'ServicesU.pas';
var
LContainer: TContainer;
LMyRequestHandler: IRequestHandler<TMyResponse, TMyRequest>;
LRequest: TMyRequest;
LResponse: TMyResponse;
begin
LContainer := TContainer.Create;
try
LRequest := TMyRequest.Create;
LRequest.MyParameter := 'Hello there!';
try
LContainer.RegisterType<TMyRequestHandler>.Implements<IRequestHandler<TMyResponse, TMyRequest>>;
LContainer.Build;
LMyRequestHandler := LContainer.Resolve<IRequestHandler<TMyResponse, TMyRequest>>;
LResponse := LMyRequestHandler.Handle(LRequest);
writeln(LResponse.MyResult);
readln;
except
on E: Exception do
writeln(E.ClassName, ': ', E.Message);
end;
finally
if Assigned(LResponse) then
LResponse.Free;
if Assigned(LRequest) then
LRequest.Free;
LContainer.Free;
end;
end.
I have defined a collection derived from TDictionary, and need to define a custom enumerator that apply an additional filter.
I'm stuck as I can't access the TDictionary FItems array (it is private) so I can't define the MoveNext method
How would you proceed to redefine a filtered enumerator on a class derived from TDictionary?
Here's a simple code to illustrate what I want to do:
TMyItem = class(TObject)
public
IsHidden:Boolean; // The enumerator should not return hidden items
end;
TMyCollection<T:TMyItem> = class(TDictionary<integer,T>)
public
function GetEnumerator:TMyEnumerator<T>; // A value filtered enumerator
type
TMyEnumerator = class(TEnumerator<T>)
private
FDictionary: TMyCollection<integer,T>;
FIndex: Integer;
function GetCurrent: T;
protected
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
constructor Create(ADictionary: TMyCollection<integer,T>);
property Current: T read GetCurrent;
function MoveNext: Boolean;
end;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext: Boolean;
begin
// In below code, FIndex is not accessible, so I can't move forward until my filter applies
while FIndex < Length(FDictionary.FItems) - 1 do
begin
Inc(FIndex);
if (FDictionary.FItems[FIndex].HashCode <> 0)
and not(FDictionary.FItems[FIndex].IsHidden) then // my filter
Exit(True);
end;
Result := False;
end;
You can base your Enumerator on TDictionary's enumerator, so you don't actually need access to FItems. This works even if you write a wrapper class around TDictionary as Barry suggests. The enumerator would look like this:
TMyEnumerator = class
protected
BaseEnumerator: TEnumerator<TPair<Integer, T>>; // using the key and value you used in your sample
public
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
function TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnumerator.MoveNext;
while Result and (not (YourTestHere)) do // ie: the base enumerator returns everything, reject stuff you don't like
Result := BaseEnumerator.MoveNext;
end;
function TMyEnumerator.Current: T;
begin
Result := BaseEnumerator.Current.Value; // Based on your example, it's value you want to extract
end;
And here's a complete, 100 lines console application that demonstrates this:
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
type
TMyType = class
public
Int: Integer;
constructor Create(anInteger:Integer);
end;
TMyCollection<T:TMyType> = class(TDictionary<integer,T>)
strict private
type
TMyEnumerator = class
protected
BaseEnum: TEnumerator<TPair<Integer,T>>;
function GetCurrent: T;
public
constructor Create(aBaseEnum: TEnumerator<TPair<Integer,T>>);
destructor Destroy;override;
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
public
function GetEnumerator: TMyEnumerator;
end;
{ TMyCollection<T> }
function TMyCollection<T>.GetEnumerator: TMyEnumerator;
begin
Result := TMyEnumerator.Create(inherited GetEnumerator);
end;
{ TMyType }
constructor TMyType.Create(anInteger: Integer);
begin
Int := anInteger;
end;
{ TMyCollection<T>.TMyEnumerator }
constructor TMyCollection<T>.TMyEnumerator.Create(aBaseEnum: TEnumerator<TPair<Integer, T>>);
begin
BaseEnum := aBaseEnum;
end;
function TMyCollection<T>.TMyEnumerator.GetCurrent: T;
begin
Result := BaseEnum.Current.Value;
end;
destructor TMyCollection<T>.TMyEnumerator.Destroy;
begin
BaseEnum.Free;
inherited;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnum.MoveNext;
while Result and ((BaseEnum.Current.Value.Int mod 2) = 1) do
Result := BaseEnum.MoveNext;
end;
var TMC: TMyCollection<TMyTYpe>;
V: TMyType;
begin
try
TMC := TMyCollection<TMyType>.Create;
try
// Fill TMC with some values
TMC.Add(1, TMyType.Create(1));
TMC.Add(2, TMyType.Create(2));
TMC.Add(3, TMyType.Create(3));
TMC.Add(4, TMyType.Create(4));
TMC.Add(5, TMyType.Create(5));
TMC.Add(6, TMyType.Create(6));
TMC.Add(7, TMyType.Create(7));
TMC.Add(8, TMyType.Create(8));
// Filtered-enum
for V in TMC do
WriteLn(V.Int);
ReadLn;
finally TMC.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
You should write a class that wraps TDictionary rather than inherits from it directly. The only reason TDictionary can be inherited from at all is so that TObjectDictionary could be defined and stay polymorphic with it. That is, the only proper support through overriding TDictionary is to customize what happens when keys and values are removed from the dictionary (so they might need to be freed).
Using Delphi 2010, let's say I've got a class declared like this:
TMyList = TList<TMyObject>
For this list Delphi kindly provides us with an enumerator, so we can write this:
var L:TMyList;
E:TMyObject;
begin
for E in L do ;
end;
The trouble is, I'd like to write this:
var L:TMyList;
E:TMyObject;
begin
for E in L.GetEnumerator('123') do ;
end;
That is, I want the ability to provide multiple enumerators for the same list, using some criteria. Unfortunately the implementation of for X in Z requires the presence of a function Z.GetEnumerator, with no parameters, that returns the given enumerator! To circumvent this problem I'm defining an interface that implements the "GetEnumerator" function, then I implement a class that implements the interface and finally I write a function on TMyList that returns the interface! And I'm returning an interface because I don't want to be bothered with manually freeing the very simple class... Any way, this requires a LOT of typing. Here's how this would look like:
TMyList = class(TList<TMyObject>)
protected
// Simple enumerator; Gets access to the "root" list
TSimpleEnumerator = class
protected
public
constructor Create(aList:TList<TMyObject>; FilterValue:Integer);
function MoveNext:Boolean; // This is where filtering happens
property Current:TTipElement;
end;
// Interface that will create the TSimpleEnumerator. Want this
// to be an interface so it will free itself.
ISimpleEnumeratorFactory = interface
function GetEnumerator:TSimpleEnumerator;
end;
// Class that implements the ISimpleEnumeratorFactory
TSimpleEnumeratorFactory = class(TInterfacedObject, ISimpleEnumeratorFactory)
function GetEnumerator:TSimpleEnumerator;
end;
public
function FilteredEnum(X:Integer):ISimpleEnumeratorFactory;
end;
Using this I can finally write:
var L:TMyList;
E:TMyObject;
begin
for E in L.FilteredEnum(7) do ;
end;
Do you know a better way of doing this? Maybe Delphi does support a way of calling GetEnumerator with a parameter directly?
Later Edit:
I decided to use Robert Love's idea of implementing the enumerator using anonymous methods and using gabr's "record" factory to save yet an other class. This allows me to create a brand new enumerator, complete with code, using just a few lines of code in a function, no new class declaration required.
Here's how my generic enumerator is declared, in a library unit:
TEnumGenericMoveNext<T> = reference to function: Boolean;
TEnumGenericCurrent<T> = reference to function: T;
TEnumGenericAnonim<T> = class
protected
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
function GetCurrent:T;
public
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
TGenericAnonEnumFactory<T> = record
public
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function GetEnumerator:TEnumGenericAnonim<T>;
end;
And here's a way to use it. On any class I can add a function like this (and I'm intentionally creating an enumerator that doesn't use a List<T> to show the power of this concept):
type Form1 = class(TForm)
protected
function Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
end;
// This is all that's needed to implement an enumerator!
function Form1.Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
var Current:Integer;
begin
Current := From - 1;
Result := TGenericAnonEnumFactory<Integer>.Create(
// This is the MoveNext implementation
function :Boolean
begin
Inc(Current);
Result := Current <= To;
end
,
// This is the GetCurrent implementation
function :Integer
begin
Result := Current;
end
);
end;
And here's how I'd use this new enumerator:
procedure Form1.Button1Click(Sender: TObject);
var N:Integer;
begin
for N in Numbers(3,10) do
Memo1.Lines.Add(IntToStr(N));
end;
See DeHL ( http://code.google.com/p/delphilhlplib/ ). You can write code that looks like this:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
Just like you can do in .NET (no syntax linq of course).
You approach is fine. I don't know of any better way.
Enumerator factory can also be implemented as a record instead of an interface.
Maybe you'll get some ideas here.
Delphi For in loop support requires on of the following: (From the Docs)
Primitive types that the compiler
recognizes, such as arrays, sets or
strings
Types that implement
IEnumerable
Types that implement the
GetEnumerator pattern as documented
in the Delphi Language Guide
If you look at Generics.Collections.pas you will find the implementation for TDictionary<TKey,TValue> where it has three enumerators for TKey, TValue, and TPair<TKey,TValue> types. Embarcadero shows that they have used verbose implementation.
You could do something like this:
unit Generics.AnonEnum;
interface
uses
SysUtils,
Generics.Defaults,
Generics.Collections;
type
TAnonEnumerator<T> = class(TEnumerator<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
TAnonEnumerable<T> = class(TEnumerable<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetEnumerator: TEnumerator<T>; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
implementation
{ TEnumerable<T> }
constructor TAnonEnumerable<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerable<T>.DoGetEnumerator: TEnumerator<T>;
begin
result := TAnonEnumerator<T>.Create(FGetCurrent,FMoveNext);
end;
{ TAnonEnumerator<T> }
constructor TAnonEnumerator<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerator<T>.DoGetCurrent: T;
begin
result := FGetCurrent(self);
end;
function TAnonEnumerator<T>.DoMoveNext: Boolean;
begin
result := FMoveNext(Self);
end;
end.
This would allow you declare your Current and MoveNext methods anonymously.
You can do away with the factory and the interface if you add a GetEnumerator() function to your enumerator, like this:
TFilteredEnum = class
public
constructor Create(AList:TList<TMyObject>; AFilterValue:Integer);
function GetEnumerator: TFilteredEnum;
function MoveNext:Boolean; // This is where filtering happens
property Current: TMyObject;
end;
and just return self:
function TFilteredEnum.GetEnumerator: TSimpleEnumerator;
begin
result := Self;
end;
and Delphi will conveniently clean up your instance for you, just like it does any other enumerator:
var
L: TMyList;
E: TMyObject;
begin
for E in TFilteredEnum.Create(L, 7) do ;
end;
You can then extend your enumerator to use an anonymous method, which you can pass in the constructor:
TFilterFunction = reference to function (AObject: TMyObject): boolean;
TFilteredEnum = class
private
FFilterFunction: TFilterFunction;
public
constructor Create(AList:TList<TMyObject>; AFilterFunction: TFilterFunction);
...
end;
...
function TFilteredEnum.MoveNext: boolean;
begin
if FIndex >= FList.Count then
Exit(False);
inc(FIndex);
while (FIndex < FList.Count) and not FFilterFunction(FList[FIndex]) do
inc(FIndex);
result := FIndex < FList.Count;
end;
call it like this:
var
L:TMyList;
E:TMyObject;
begin
for E in TFilteredEnum.Create(L, function (AObject: TMyObject): boolean
begin
result := AObject.Value = 7;
end;
) do
begin
//do stuff here
end
end;
Then you could even make it a generic, but I wont do that here, my answer is long enough as it is.
N#
I use this approach...where the AProc performs the filter test.
TForEachDataItemProc = reference to procedure ( ADataItem: TDataItem; var AFinished: boolean );
procedure TDataItems.ForEachDataItem(AProc: TForEachDataItemProc);
var
AFinished: Boolean;
ADataItem: TDataItem;
begin
AFinished:= False;
for ADataItem in FItems.Values do
begin
AProc( ADataItem, AFinished );
if AFinished then
Break;
end;
end;
I've got a little class hierarchy where each class corresponds to a certain TComponent descendent (say base class TDefaultFrobber with descendents TActionFrobber and TMenuItemFrobber, corresponding to TComponent, TCustomAction and TMenuItem, respectively). Now I want a factory (?) function something like this:
function CreateFrobber(AComponent: TComponent): IFrobber;
begin
if AComponent is TCustomAction then
Result := TActionFrobber.Create(TCustomAction(AComponent))
else if AComponent is TMenuItem then
Result := TMenuItemFrobber.Create(TMenuItem(AComponent))
else
Result := TDefaultFrobber.Create(AComponent);
end;
Can I somehow refactor this to use virtual functions or something similar instead of the if-else cascade or RTTI?
Edit: My solution for now:
unit Frobbers;
interface
uses
Classes;
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass; AFrobberClass: TComponentFrobberClass); static;
end;
implementation
uses
ActnList,
Menus;
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
constructor TComponentFrobber.Create(AComponent: TComponent);
begin
inherited Create;
FComponent := AComponent;
end;
class function TComponentFrobber.FindFrobberClass(AComponent: TComponent): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if AComponent is FComponentFrobberRegistry[i].ComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
class procedure TComponentFrobber.RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass);
var
i: Integer;
begin
Assert(FindFrobberClass(AComponentClass) = nil, 'Duplicate Frobber class');
i := Length(FComponentFrobberRegistry);
SetLength(FComponentFrobberRegistry, Succ(i));
FComponentFrobberRegistry[i].ComponentClass := AComponentClass;
FComponentFrobberRegistry[i].FrobberClass := AFrobberClass;
end;
function CreateComponentFrobber(AComponent: TComponent): IComponentFrobber;
var
FrobberClass: TComponentFrobberClass;
begin
FrobberClass := TComponentFrobber.FindFrobberClass(AComponent);
Assert(FrobberClass <> nil);
Result := FrobberClass.Create(AComponent);
end;
type
TActionFrobber = class(TComponentFrobber);
TMenuItemFrobber = class(TComponentFrobber);
initialization
TComponentFrobber.RegisterFrobber(TCustomAction, TActionFrobber);
TComponentFrobber.RegisterFrobber(TMenuItem, TMenuItemFrobber);
end.
Thanks to Cesar, Gamecat and mghie.
If you create a class with a virtual constructor and create a class type for that class. You can create a lookuplist based on the component class name.
Example:
type
TFrobber = class
public
constructor Create; virtual;
class function CreateFrobber(const AComponent: TComponent): TFrobber;
end;
TFrobberClass = class of TFrobber;
type
TFrobberRec = record
ClassName: ShortString;
ClassType: TFrobberClass;
end;
const
cFrobberCount = 3;
cFrobberList : array[1..cFrobberCount] of TFrobberRec = (
(ClassName : 'TAction'; ClassType: TActionFrobber),
(ClassName : 'TButton'; ClassType: TButtonFrobber),
(ClassName : 'TMenuItem'; ClassType: TMenuItemFrobber)
);
class function TFrobber.CreateFrobber(const AComponent: TComponent): TFrobber;
var
i : Integer;
begin
Result := nil;
for i := 1 to cFrobberCount do begin
if AComponent.ClassName = cFrobberList[i].ClassName then begin
Result := cFrobberList[i].ClassType.Create();
Exit;
end;
end;
end;
You can of course also work with a dynamic list (dictionary) but then you must register each combination somehow.
Update
To commnent on the remarks of mghie.
You are perfectly right. But this is not possibly without really ugly tricks.
Right now you have to use the initialization/finalization sections of a unit to regoister a class. But it would be cool to add a initialization/finalization class method to a class. These have to be called along with the initialization (and finalization) of the unit. Like this:
class
TFrobber = class
private
initialization Init; // Called at program start just after unit initialization
finalization Exit; // called at program end just before unit finalization.
end;
2 suggestions:
Make class pair array of classes, then you can get the Index and use the pair of the class constructor,
var
ArrayItem: array[0..1] of TComponentClass = (TActionFrobber, TMenuItemFrobber);
ArrayOwner: array[0..1] of TComponentClass = (TCustomAction, TMenuItem);
function CreateFrobber(AComponent: TComponentClass): IFrobber;
var
Index: Integer;
begin
Result:= nil;
for I := Low(ArrayOwner) to High(ArrayOwner) do
if AComponent is ArrayOwner[I] then
begin
Result:= ArrayItem[I].Create(AComponent);
Break;
end;
if Result = nil then
Result:= TDefaultFrobber.Create(AComponent);
end;
or use RTTI + ClassName conventions, like this:
function CreateFrobber(AComponent: TComponentClass): IFrobber;
const
FrobberClassSuffix = 'Frobber';
var
LClass: TComponentClass;
LComponent: TComponent;
begin
LClass:= Classes.FindClass(AComponent.ClassName + FrobberClassSuffix);
if LClass <> nil then
LComponent:= LClass.Create(AComponent)
else
LComponent:= TDefaultFrobber.Create(AComponent);
if not Supports(LComponent, IFrobber, Result) then
Result:= nil;
end;
I'd like to add some comments to your current solution, answering here as this can not really be done in the comments section:
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent):
TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass); static;
end;
There is not much point in using TInterfacedObject for the base class, as you will always need the object, not the interface it implements - how else would you find your concrete Frobber class? I would split this into TComponentFrobber, descending from TInterfacedObject, and a TComponentRegistry class (descending from TObject) that has the class methods. You can then of course make the registry class more generic, it is not tied to TComponentFrobber and could be reused.
Edit: I have used similar class registries for example when loading files: load the identifier for the next object (could be for example string, integer or GUID), then get the correct class to instantiate from the registry, then create and load the object.
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
This is OK if you will never add or remove classes to / from the registry, but generally I would not use an array but a list for the registry entries.
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
Searching backwards in the array will not help for finding the most specialized frobber, unless you add them in the correct order (least specialized first). Why don't you check for the ClassType being equal? There is also ClassParent to traverse the class hierarchy, if you need to test for base classes too.
suppose I have a TModel:
TModelClass = class of TModel;
TModel = class
procedure DoSomeStuff;
end;
and 2 descendants:
TModel_A = class(TModel);
TModel_B = class(TModel);
and a factory :
TModelFactory = class
class function CreateModel_A: TModel_A;
class function CreateModel_B: TModel_B;
end;
Now I want to refactor a bit :
TModelFactory = class
class function CreateGenericModel(Model: TModelClass) : TModel
end;
class function TModelFactory.CreateGenericModel(Model: TModelClass) : TModel
begin
...
case Model of
TModel_A: Result := TModel_A.Create;
TModel_B: Result := TModel_B.Create;
end;
...
end;
So far it's ok, but every time I create a TModel descendant, I have to modify the factory case statement.
My question: Is this possible to create a 100% generic factory for all my TModel descendants, so every time I create a TModel descendants I don't have to modify TModelFactory ?
I tried to play with Delphi 2009 generics but didn't find valuable information, all are related to basic usage of TList<T>and so on.
Update
Sorry, but maybe I'm not clear or don't understand your answer (I'm still a noob), but what i'm trying to achieve is :
var
M: TModel_A;
begin
M: TModelFactory.CreateGenericModel(MY_CONCRETE_CLASS);
Well, you could write
class function TModelFactory.CreateGenericModel(AModelClass: TModelClass): TModel;
begin
Result := AModelClass.Create;
end;
but then you don't need a factory any more. Usually one would have a selector of a different type, like an integer or string ID, to select the concrete class the factory should create.
Edit:
To answer your comment on how to add new classes without the need to change the factory - I will give you some simple sample code that works for very old Delphi versions, Delphi 2009 should upen up much better ways to do this.
Each new descendant class only needs to be registered with the factory. The same class can be registered using several IDs. The code uses a string ID, but integers or GUIDs would work just as well.
type
TModelFactory = class
public
class function CreateModelFromID(const AID: string): TModel;
class function FindModelClassForId(const AID: string): TModelClass;
class function GetModelClassID(AModelClass: TModelClass): string;
class procedure RegisterModelClass(const AID: string;
AModelClass: TModelClass);
end;
{ TModelFactory }
type
TModelClassRegistration = record
ID: string;
ModelClass: TModelClass;
end;
var
RegisteredModelClasses: array of TModelClassRegistration;
class function TModelFactory.CreateModelFromID(const AID: string): TModel;
var
ModelClass: TModelClass;
begin
ModelClass := FindModelClassForId(AID);
if ModelClass <> nil then
Result := ModelClass.Create
else
Result := nil;
end;
class function TModelFactory.FindModelClassForId(
const AID: string): TModelClass;
var
i, Len: integer;
begin
Result := nil;
Len := Length(RegisteredModelClasses);
for i := 0 to Len - 1 do
if RegisteredModelClasses[i].ID = AID then begin
Result := RegisteredModelClasses[i].ModelClass;
break;
end;
end;
class function TModelFactory.GetModelClassID(AModelClass: TModelClass): string;
var
i, Len: integer;
begin
Result := '';
Len := Length(RegisteredModelClasses);
for i := 0 to Len - 1 do
if RegisteredModelClasses[i].ModelClass = AModelClass then begin
Result := RegisteredModelClasses[i].ID;
break;
end;
end;
class procedure TModelFactory.RegisterModelClass(const AID: string;
AModelClass: TModelClass);
var
i, Len: integer;
begin
Assert(AModelClass <> nil);
Len := Length(RegisteredModelClasses);
for i := 0 to Len - 1 do
if (RegisteredModelClasses[i].ID = AID)
and (RegisteredModelClasses[i].ModelClass = AModelClass)
then begin
Assert(FALSE);
exit;
end;
SetLength(RegisteredModelClasses, Len + 1);
RegisteredModelClasses[Len].ID := AID;
RegisteredModelClasses[Len].ModelClass := AModelClass;
end;
Result := Model.Create;
should work, too.
The solution with Model.Create works if the constructor is virtual.
If you use delphi 2009, you can use another trick using generics:
type
TMyContainer<T: TModel, constructor> (...)
protected
function CreateModel: TModel;
end;
function TMyContainer<T>.CreateModel: TModel;
begin
Result := T.Create; // Works only with a constructor constraint.
end;
If I understand your question properly, I wrote something similar here http://www.malcolmgroves.com/blog/?p=331
There is probably a simpler way to accomplish this. I seem to remember finding the built-in TClassList object that handled this, but that this point I already had this working. TClassList does not have a way to look up the stored objects by the string name, but it could still be useful.
Basically to make this work you need to register your classes with a global object. That way it can take a string input for the class name, lookup that name in a list to find the correct class object.
In my case I used a TStringList to hold the registered classes and I use the class name as the identifier for the class. In order to add the class to the "object" member of the string list I needed to wrap the class in a real object. I'll admit that I don't really understand the "class" so this may not be needed if you cast everything right.
// Needed to put "Class" in the Object member of the
// TStringList class
TClassWrapper = class(TObject)
private
FGuiPluginClass: TAgCustomPluginClass;
public
property GuiPluginClass: TAgCustomPluginClass read FGuiPluginClass;
constructor Create(GuiPluginClass: TAgCustomPluginClass);
end;
I have a global "PluginManager" object. This is where classes get registered and created. The "AddClass" method puts the class in the TStringList so I can look it up later.
procedure TAgPluginManager.AddClass(GuiPluginClass: TAgCustomPluginClass);
begin
FClassList.AddObject(GuiPluginClass.ClassName,
TClassWrapper.Create(GuiPluginClass));
end;
In each class that I create I add it to the class list in the "initialization" section.
initialization;
AgPluginManager.AddClass(TMyPluginObject);
Then, when it comes time to create the class I can lookup the name in the string list, find the class and create it. In my actual function I am checking to make sure the entry exists and deal with errors, etc. I am also passing in more data to the class constructor. In my case I am creating forms so I don't actually return the object back to the caller (I track them in my PluginManager), but that would be easy to do if needed.
procedure TAgPluginManager.Execute(PluginName: string);
var
ClassIndex: integer;
NewPluginWrapper: TClassWrapper;
begin
ClassIndex := FClassList.IndexOf(PluginName);
if ClassIndex > -1 then
begin
NewPluginWrapper := TClassWrapper(FClassList.Objects[ClassIndex]);
FActivePlugin := NewPluginWrapper.GuiPluginClass.Create();
end;
end;
Since I first wrote this I have not needed to touch the code. I just make sure to add my new classes to the list in their initialization section and everything works.
To create an object I just call
PluginManger.Execute('TMyPluginObject');
You can do generic factory like this: But the only issue you should set the generic construct method to it for each of the factory final class like this:
type
TViewFactory = TGenericFactory<Integer, TMyObjectClass, TMyObject>;
...
F := TViewFactory.Create;
F.ConstructMethod :=
function(AClass: TMyObjectClass; AParams: array of const): TMyObject
begin
if AClass = nil then
Result := nil
else
Result := AClass.Create;
end;
and the unit for the factory is:
unit uGenericFactory;
interface
uses
System.SysUtils, System.Generics.Collections;
type
EGenericFactory = class(Exception)
public
constructor Create; reintroduce;
end;
EGenericFactoryNotRegistered = class(EGenericFactory);
EGenericFactoryAlreadyRegistered = class(EGenericFactory);
TGenericFactoryConstructor<C: constructor; R: class> = reference to function(AClass: C; AParams: array of const): R;
TGenericFactory<T; C: constructor; R: class> = class
protected
FType2Class: TDictionary<T, C>;
FConstructMethod: TGenericFactoryConstructor<C, R>;
procedure SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
public
constructor Create(AConstructor: TGenericFactoryConstructor<C, R> = nil); reintroduce; overload; virtual;
destructor Destroy; override;
procedure RegisterClass(AType: T; AClass: C);
function ClassForType(AType: T): C;
function TypeForClass(AClass: TClass): T;
function SupportsClass(AClass: TClass): Boolean;
function Construct(AType: T; AParams: array of const): R;
property ConstructMethod: TGenericFactoryConstructor<C, R> read FConstructMethod write SetConstructMethod;
end;
implementation
uses
System.Rtti;
{ TGenericFactory<T, C, R> }
function TGenericFactory<T, C, R>.ClassForType(AType: T): C;
begin
FType2Class.TryGetValue(AType, Result);
end;
function TGenericFactory<T, C, R>.Construct(AType: T; AParams: array of const): R;
begin
if not Assigned(FConstructMethod) then
Exit(nil);
Result := FConstructMethod(ClassForType(AType), AParams);
end;
constructor TGenericFactory<T, C, R>.Create(AConstructor: TGenericFactoryConstructor<C, R> = nil);
begin
inherited Create;
FType2Class := TDictionary<T, C>.Create;
FConstructMethod := AConstructor;
end;
destructor TGenericFactory<T, C, R>.Destroy;
begin
FType2Class.Free;
inherited;
end;
procedure TGenericFactory<T, C, R>.RegisterClass(AType: T; AClass: C);
begin
if FType2Class.ContainsKey(AType) then
raise EGenericFactoryAlreadyRegistered.Create;
FType2Class.Add(AType, AClass);
end;
procedure TGenericFactory<T, C, R>.SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
begin
FConstructMethod := Value;
end;
function TGenericFactory<T, C, R>.SupportsClass(AClass: TClass): Boolean;
var
Key: T;
Val: C;
begin
for Key in FType2Class.Keys do
begin
Val := FType2Class[Key];
if CompareMem(#Val, AClass, SizeOf(Pointer)) then
Exit(True);
end;
Result := False;
end;
function TGenericFactory<T, C, R>.TypeForClass(AClass: TClass): T;
var
Key: T;
Val: TValue;
begin
for Key in FType2Class.Keys do
begin
Val := TValue.From<C>(FType2Class[Key]);
if Val.AsClass = AClass then
Exit(Key);
end;
raise EGenericFactoryNotRegistered.Create;
end;
{ EGenericFactory }
constructor EGenericFactory.Create;
begin
inherited Create(Self.ClassName);
end;
end.