Make Delphi TList immutable - delphi

is it possible to make a Delphi TList immutable?
I searched in the delphi doc for a class similar to the unmodifiableList in java, but didn't found anything.
regards!

You could use the IReadOnlyList<T> from Spring4D.
If you have an IList<T> you just call AsReadOnlyList (AsReadOnly since 2.0) and it returns you the same instance as IReadOnlyList<T> which does not provide methods to manipulate the list (no Add, Delete or setter for the Items property).
However there is a difference to the unmodifiableList from Java:
In Java you really get a List<T> which will throw UnsupportedOperationException when you try to modify it while in Spring4D which is mostly modeled after .NET you get something that you cannot call any modifying operations on.

The Delphi RTL contains no classes that implement immutable or read-only lists. You will have to implement such a class yourself, or find a library that offers such functionality.

Here is a simple Generic immutable list implementation with Map & Filter support :
unit Immutable;
interface
uses
System.Generics.Collections;
type
TFilter<TItem> = reference to function(AItem: TItem): Boolean;
TMapper<TItem> = reference to function(AItem: TItem): TItem;
IImmutableList<TItem> = interface
function Insert(Index: Integer; AItem: TItem): IImmutableList<TItem> ;
function Filter(AFilter: TFilter<TItem>): IImmutableList<TItem> ;
function Map(AMapper: TMapper<TItem>): IImmutableList<TItem> ;
function GetEnumerator: TEnumerator<TItem>;
end;
TImmutableList<TItem> = class(TInterfacedObject, IImmutableList<TItem>)
private
FList: TList<TItem>;
public
constructor Create(); overload;
constructor Create(AImmutableList: IImmutableList<TItem>); overload;
destructor Destroy; override;
function Insert(Index: Integer; AItem: TItem): IImmutableList<TItem>;
function Filter(AFilter: TFilter<TItem>): IImmutableList<TItem>;
function Map(AMapper: TMapper<TItem>): IImmutableList<TItem>;
function GetEnumerator: TEnumerator<TItem>;
end;
implementation
{ TImmutableList<TItem> }
constructor TImmutableList<TItem>.Create;
begin
FList := TList<TItem>.Create;
end;
constructor TImmutableList<TItem>.Create(AImmutableList: IImmutableList<TItem>);
var
AItem : TItem;
begin
FList := TList<TItem>.Create;
for AItem in AImmutableList do
FList.Add(AItem);
end;
destructor TImmutableList<TItem>.Destroy;
begin
FList.Free;
inherited;
end;
function TImmutableList<TItem>.GetEnumerator: TEnumerator<TItem>;
begin
Result := FList.GetEnumerator;
end;
function TImmutableList<TItem>.Insert(Index: Integer; AItem: TItem): IImmutableList<TItem>;
var
NewList : TImmutableList<TItem>;
begin
NewList := TImmutableList<TItem>.Create(Self);
TImmutableList<TItem>(NewList).FList.Insert(Index, AItem);
Result := NewList;
end;
function TImmutableList<TItem>.Filter(AFilter: TFilter<TItem>): IImmutableList<TItem>;
var
AItem : TItem;
NewList : TImmutableList<TItem>;
begin
NewList := TImmutableList<TItem>.Create();
for AItem in FList do begin
if AFilter(AItem) then
TImmutableList<TItem>(NewList).FList.Add(AItem)
end;
Result := NewList;
end;
function TImmutableList<TItem>.Map(AMapper: TMapper<TItem>): IImmutableList<TItem>;
var
AItem : TItem;
NewList : TImmutableList<TItem>;
begin
NewList := TImmutableList<TItem>.Create();
for AItem in FList do begin
TImmutableList<TItem>(NewList).FList.Add( AMapper(AItem))
end;
Result := NewList;
end;
end.
I'm using it in https://github.com/pierrejean-coudert/ReduxDelphi TodoMVC sample code.

Related

Compiler not mapping a class method to an interface method

I am using Delphi Pro 10.2.3 Tokyo. I want to create a TDataset wrapper class which I can use to enumerate through a list of IData descendants with a for-in loop. When I try to compile the code below, I get the following error message.
[dcc32 Error] Core.Data.DatasetAdapter.pas(25): E2291 Missing implementation of interface method IEnumerator.GetCurrent
Clearly, GetCurrent is implemented. Any idea how to fix this?
unit Core.Data.DatasetAdapter;
interface
uses
Data.Db
;
type
IData = interface
['{15D1CF4F-B9E1-4525-B035-24B9A6584325}']
end;
IDataList<T: IData> = interface
['{9FEE9BB1-A983-4FEA-AEBF-4D3AF5219444}']
function GetCount: Integer;
function GetCurrent: T;
procedure Load;
procedure Unload;
property Count: Integer read GetCount;
property Current: T read GetCurrent;
end;
TDatasetAdapter<T: IData> = class(
TInterfacedObject
, IData, IDataList<T>
, IEnumerator<T>
)
private
FBof: Boolean;
FDataset: TDataset;
FIntf: T;
function GetCount: Integer;
function GetCurrent: T;
function GetEof: Boolean;
function GetInterface: T;
function MoveNext: Boolean;
procedure Reset;
protected
function FieldByName(const FieldName: string): TField;
procedure MapFields; virtual;
property Dataset: TDataset read FDataset;
public
constructor Create(ADataset: TDataset); virtual;
function GetEnumerator: IEnumerator<T>;
procedure Cancel;
procedure Close;
procedure Delete;
procedure Edit;
procedure First;
procedure Insert;
procedure Load;
procedure Next;
procedure Open;
procedure Post;
procedure UnLoad;
property Count: Integer read GetCount;
property Eof: Boolean read GetEof;
end;
implementation
uses
System.SysUtils
, System.TypInfo
;
{ TDatasetAdapter<T> }
{
****************************** TDatasetAdapter<T> ******************************
}
constructor TDatasetAdapter<T>.Create(ADataset: TDataset);
begin
FDataset := ADataset;
FIntf := GetInterface;
end;
procedure TDatasetAdapter<T>.Cancel;
begin
FDataset.Cancel;
end;
procedure TDatasetAdapter<T>.Close;
begin
FDataset.Close;
end;
procedure TDatasetAdapter<T>.Delete;
begin
FDataset.Delete;
end;
procedure TDatasetAdapter<T>.Edit;
begin
FDataset.Edit;
end;
function TDatasetAdapter<T>.FieldByName(const FieldName: string): TField;
begin
Result := FDataset.FieldByName(FieldName);
end;
procedure TDatasetAdapter<T>.First;
begin
FDataset.First;
end;
function TDatasetAdapter<T>.GetCount: Integer;
begin
Result := FDataset.RecordCount;
end;
function TDatasetAdapter<T>.GetCurrent: T;
begin
Result := FIntf;
end;
function TDatasetAdapter<T>.GetEnumerator: IEnumerator<T>;
begin
Reset;
Result := Self;
end;
function TDatasetAdapter<T>.GetEof: Boolean;
begin
Result := FDataset.Eof;
end;
function TDatasetAdapter<T>.GetInterface: T;
var
LGuid: TGuid;
begin
LGuid := GetTypeData(TypeInfo(T))^.Guid;
if not Supports(Self, LGuid, Result) then
Result := nil;
end;
procedure TDatasetAdapter<T>.Insert;
begin
FDataset.Insert;
end;
procedure TDatasetAdapter<T>.Load;
begin
Open;
MapFields;
end;
procedure TDatasetAdapter<T>.MapFields;
begin
//Stub procedure
end;
function TDatasetAdapter<T>.MoveNext: Boolean;
begin
if FBof then FBof := False
else Next;
Result := not Eof;
end;
procedure TDatasetAdapter<T>.Next;
begin
FDataset.Next;
end;
procedure TDatasetAdapter<T>.Open;
begin
FDataset.Open;
end;
procedure TDatasetAdapter<T>.Post;
begin
FDataset.Post;
end;
procedure TDatasetAdapter<T>.Reset;
begin
FBof := True;
First;
end;
procedure TDatasetAdapter<T>.UnLoad;
begin
Close;
end;
end.
You need to resolve function GetCurrent: T twice: for IDataList<T> and for Enumerator<T>. But you also need one for the non-generic ancestor of IEnumerator<T>: IEnumerator. Apparently that is not hidden by the GetCurrent method of IEnumerator<T>.
Try method resolution clauses:
function GetGenericCurrent: T; // implement this
function IDataList<T>.GetCurrent = GetGenericCurrent;
function IEnumerator<T>.GetCurrent = GetGenericCurrent;
function GetCurrent: TObject; // implement this -- can return nil.
The implementation of both can be the same, but you will have to make two methods. The one for the non-generic IEnumerator can return nil.
Update
I had to modify the code above. Now it should work. It is not necessary to have two implementations for GetCurrent returning T, but you must have one returning TObject.

How can the subclass constructor be called from the parent class?

Is there a way to invoke Create of the subclass from the parent class? Below there is this Duplicate method in which I want the constructor of the subclass to be invoked instead, so that the test at the bottom succeeds.
type
IBla<T> = interface(IInvokable)
['{34E812BF-D021-422A-A051-A492F25534C4}']
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassA<T> = class(TInterfacedObject, IBla<T>)
protected
function GetInt(): Integer; virtual;
public
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassB = class(TClassA<Integer>, IBla<Integer>)
protected
function GetInt(): Integer; override;
end;
function TClassA<T>.Duplicate: IBla<T>;
begin
Exit(TClassA<T>.Create());
end;
function TClassA<T>.GetInt: Integer;
begin
Exit(1);
end;
function TClassA<T>.GetIntFromIface: Integer;
begin
Exit(GetInt());
end;
function TClassB.GetInt: Integer;
begin
Exit(2);
end;
procedure TestRandomStuff.Test123;
var
o1, o2: IBla<Integer>;
begin
o1 := TClassB.Create();
o2 := o1.Duplicate();
Assert.AreEqual(o2.GetIntFromIface, 2);
end;
You can do this using RTTI:
uses
System.Rtti;
....
function TClassA<T>.Duplicate: IBla<T>;
var
ctx: TRttiContext;
typ: TRttiType;
mthd: TRttiMethod;
inst: TValue;
begin
typ := ctx.GetType(ClassInfo);
mthd := typ.GetMethod('Create');
inst := mthd.Invoke((typ as TRttiInstanceType).MetaclassType, []);
inst.AsObject.GetInterface(IBla<T>, Result);
end;
There is quite probably a cleaner way to invoke a constructor using RTTI (I know next to nothing about RTTI in Delphi), so you might do well to read around that topic rather than taking the above as being the canonical way to do this.
Of course, this assumes that all subclasses use a parameterless constructor defined in TObject. That might be rather limiting. I would not be surprised if you found yourself having to re-think the design in a more fundamental manner.
If none of your subclasses implement constructors then you could make it even simpler, and not use RTTI at all:
function TClassA<T>.Duplicate: IBla<T>;
begin
ClassType.Create.GetInterface(IBla<T>, Result);
end;
But be aware that this calls the constructor defined in TObject and will not call any constructor defined in a subclass.
This seems to work:
function TClassA<T>.Duplicate: IBla<T>;
begin
//Exit(TClassA<T>.Create());
Exit( ClassType.Create as TClassA<T> );
end;
The subtlety is that ClassType.Create will create (in this case) a TClassB and the original creates a TClassA< integer > which the compiler sees as different to TClassB, and hence calls TClassA< T >.GetInt rather than TClassB.GetInt.
Edit
But be aware that this calls the constructor defined in TObject and will not call any constructor defined in a subclass. (With thanks to David H)
However, here is solution that overcomes that restriction too:
interface
type
IBla<T> = interface(IInvokable)
['{34E812BF-D021-422A-A051-A492F25534C4}']
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassA<T> = class(TInterfacedObject, IBla<T>)
protected
function GetInt(): Integer; virtual;
public
constructor Create; virtual;
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
//TClassB = class(TClassA<Integer>)
TClassB = class(TClassA<Integer>, IBla<Integer>)
protected
function GetInt(): Integer; override;
public
constructor Create; override;
function Duplicate(): IBla<Integer>;
end;
procedure Test123;
implementation
constructor TClassA<T>.Create;
begin
inherited Create;
end;
function TClassA<T>.Duplicate: IBla<T>;
begin
Exit(TClassA<T>.Create());
end;
function TClassA<T>.GetInt: Integer;
begin
Exit(1);
end;
function TClassA<T>.GetIntFromIface: Integer;
begin
Exit(GetInt());
end;
constructor TClassB.Create;
begin
inherited Create;
end;
function TClassB.Duplicate: IBla<Integer>;
begin
Result := TClassB.Create;
end;
function TClassB.GetInt: Integer;
begin
Exit(2);
end;
procedure Test123;
var
o1, o2: IBla<Integer>;
begin
o1 := TClassB.Create();
o2 := o1.Duplicate();
Assert( o2.GetIntFromIface = 2);
end;

Delphi copy generic object with unknown base type at compile time

I would like to copy generic object but its type can only be obtained by the "class of" construct at runtime as the source object type may be different (TItem or TSpecificItem etc.):
type
TItem = class
//...
procedure Assign(Source: TItem);virtual; abstract; //edit
end;
TSpecificItem = class(TItem)
//...
end;
TEvenMoreSpecificItem = class(TSpecificItem)
//...
end;
TItemClass = class of TItem;
TItemContainer = class
FItems: TObjectList<TItem>; //edit
procedure Assign(Source: TObject); //edit
function GetItem(Index: Integer): TItem; inline; //edit
procedure SetItem(Index: Integer; Item: TItem); inline; //edit
function Count: Integer; //edit;
function ItemClass: TItemClass; virtual; abstract;
property Items[Index: Integer]: TItem read GetItem write SetItem; //edit
end;
TItemContainer<T: TItem> = class(TItemContainer)
//...
function GetItem(Index: Integer): T; inline; //edit
procedure SetItem(Index: Integer; Item: T); inline; //edit
function ItemClass: TItemClass; override;
property Items[Index: Integer]: T read GetItem write SetItem; default; //edit
end;
//start of edit
function TItemContainer.Count: Integer;
begin
Result := FItems.Count;
end;
function TItemContainer.GetItem(Index: Integer): TItem;
begin
Result := FItems[Index];
end;
procedure TItemContainer.SetItem(Index: Integer; Item: TItem);
begin
FItems[Index].Assign(Item);
end;
procedure TItemContainer.Assign(Source: TObject);
var
I: Integer;
Item: TItem;
Cls: TClass;
begin
if Source is TItemContainer then
begin
FItems.Clear;
for I := 0 to TItemContainer(Source).Count - 1 do
begin
Item := TItemContainer(Source).Items[I];
Cls := Item.ClassType;
Item := TItemClass(Cls).Create;
Item.Assign(TItemContainer(Source).Items[I]);
FItems.Add(Item);
end;
end;
end;
function TItemContainer<T>.GetItem(Index: Integer): T;
begin
Result := T(inherited GetItem(Index));
end;
procedure TItemContainer<T>.SetItem(Index: Integer; Item: T);
begin
inherited SetItem(Index, Item);
end;
//end of edit
function TItemContainer<T>.ItemClass: TItemClass;
begin
Result := TItemClass(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType);
end;
function CopyGenericObject(Source: TItemContainer): TItemContainer;
var
Cls: TItemClass;
begin
Cls := Source.ItemClass;
Result := TItemContainer<Cls>.Create; // compiler reports error "incompatible types"
Result.Assign(Source);
end;
// edit:
procedure DoCopy;
var
Source: TItemContainer<TEvenMoreSpecificItem>;
Dest: TItemContainer;
begin
Source := TItemContainer<TEvenMoreSpecificItem>.Create; // for example
//add some items to Source
Dest := CopyGenericObject(Source);
//use the result somewhere
end;
I must Use Delphi XE.
I've found
http://docwiki.embarcadero.com/RADStudio/XE6/en/Overview_of_Generics
Dynamic instantiation
Dynamic instantiation at run time is not supported.
Is it what I want to do?
If I understand well, what you are looking for is to implement a routine that will create an instance of a class of the same type as a given source. This can be done like this :
type
TItemContainerclass = class of TItemContainer;
function CopyGenericObject(Source: TItemContainer): TItemContainer;
begin
Result := TItemContainerclass(Source.ClassType).Create;
end;
Also, you can simplify the ItemClass routine to
function TItemContainer<T>.ItemClass: TItemClass;
begin
Result := T;
end;
Note that this will only create a new instance and not a copy of the source, but since your code doesn't show any attempt to copy the object and only create a new instance, I presumed this is your intended result.
Note : This works in Delphi 10, I don't have access to XE to test it.
The line
Cls := Source.ItemClass;
will create the TItemClass instance at run time only. For Generics, the compiler needs to know the type at compile time. Without knowing it, the compiler can not generate the binary code which implements your specific TItemContainer<Cls>. Or, said in other words, Cls must not be a variable, it has to be a specific class type, known at compile time.
So for example these will compile:
Result := TItemContainer<TSpecificItem>.Create;
or
Result := TItemContainer<TEvenMoreSpecificItem>.Create;
but not this
Result := TItemContainer</* type will be known later */>.Create;
because the compiler is not able to come back later and complete the binary application code based on the actual type of Cls.
You can make CopyGenericObject function as a method of your generic object instead of stand-alone function:
TItemContainer<T: TItem> = class(TItemContainer)
...
function Copy: TItemContainer<T>;
end;
In this case, it "knows" at compile-time, what class to create just because there are now several of them (one for each Instantiated type) after compiler did its work, each making copy of itself.
There is one more trick which may be useful in your case: how to copy various objects. For example, you have common class TAnimal and its descendants: TCat and TDog. You store them in TItemContainer, that's the whole point of inheritance that you can do it and treat them generally. Now, you want to implement creating a copy of this container and you don't know at compile time, which elements will be dogs and which will be cats. Standart method is to define abstract function Copy in TAnimal:
TAnimal = class
public
...
function Copy: TAnimal; virtual; abstract;
end;
and then implement it in each descendant, so then you can copy your TItemContainer like this:
function TItemContainer<T>.Copy: TItemContainer<T>;
var i: T;
begin
Result:=TItemContainer<T>.Create;
for i in Items do
//I don't know exact structure of your container,
//maybe that's more like
// for j:=0 to Count-1 do begin
// i:=Items[j];
//but I hope it's obvious what happens here
Result.Add(i.copy as T);
end;
So if you have container of cats, then i.copy will return TAnimal (but actually a cat) which will be cast to TCat at last. It works but a bit ugly.
In delphi I came up with better solution: make this copy a constructor, not a function:
TAnimal = class
public
...
constructor Copy(source: TAnimal); virtual;
end;
In that case copying your container is like this:
function TItemContainer<T>.Copy: TItemContainer<T>;
var i,j: T;
begin
Result:=TItemContainer<T>.Create;
for i in Items do
Result.Add(T.Copy(i));
end;
no extra casting which is good. What's more, you can for example derive your classes from TPersistent and implement Assign procedure everywhere you need (very useful thing) and then once and for all write a copy constructor:
TAnimal = class(TPersistent)
public
constructor Copy(source: TPersistent); //or maybe source: TAnimal
end;
//implementation
constructor TAnimal.Copy(source: TPersistent);
begin
Create;
Assign(source);
end;

How can I convert from generic to Variant in Delphi

I have a Delphi generic class that exposes a function with an argument of the generic type. Inside this function, I need to pass an instance of the generic type on to another object expecting a Variant type. Similar to this:
type
IMyInterface = interface
DoStuff(Value: Variant);
end;
TMyClass<T> = class
FMyIntf: IMyInterface
procedure DoStuff(SomeValue: T);
end;
[...]
procedure MyClass<T>.DoStuff(SomeValue: T);
begin
FMyIntf.DoStuff((*convert SomeValue to Variant here*));
end;
I tried using Rtti.TValue.From(SomeValue).AsVariant. This worked for integral types, but blew up for Booleans. I don't quite see why, since normally I'd be able to assign a Boolean value to a Variant...
Is there a better way to make this conversion? I only need it to work for simple built-in types (excluding enumerations and records)
I think there is no direct way to convert generic type to variant because variant cannot hold all the possible types. You must write your specific conversion routine. E.g.:
interface
//...
type
TDemo = class
public
class function GetAsVariant<T>(const AValue: T): Variant;
end;
//...
implementation
uses
Rtti,
TypInfo;
//...
{ TDemo}
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
bRes: Boolean;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkInteger: Result := val.AsInteger;
tkInt64: Result := val.AsInt64;
tkEnumeration:
begin
if val.TryAsType<Boolean>(bRes) then
Result := bRes
else
Result := val.AsOrdinal;
end;
tkFloat: Result := val.AsExtended;
tkString, tkChar, tkWChar, tkLString, tkWString, tkUString:
Result := val.AsString;
tkVariant: Result := val.AsVariant
else
begin
raise Exception.Create('Unsupported type');
end;
end;
end;
Because TValue.AsVariant handles most of the type conversions internally, this function can be simplified. I will handle enumerations in case you could need them later:
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkEnumeration:
begin
if val.TypeInfo = TypeInfo(Boolean) then
Result := val.AsBoolean
else
Result := val.AsOrdinal;
end
else
begin
Result := val.AsVariant;
end;
end;
Possible usage:
var
vValue: Variant;
begin
vValue := TDemo.GetAsVariant<Boolean>(True);
Assert(vValue = True); //now vValue is a correct Boolean
Looks like in my Delphi version 10.2 the Boolean problem is gone and TValue.From<T>(FValue).AsVariant is enough.
Here an example with some other helpful things like comparing the generic type:
TMyValue<T> = class(TPersistent)
private
FValue: T;
procedure SetValue(const AValue: T);
function GetAsVariant: Variant; override;
public
procedure Assign(Source: TPersistent); override;
property Value: T read FValue write SetValue;
property AsVariant: Variant read GetAsVariant;
end;
function TMyValue<T>.GetAsVariant: Variant;
begin
Result:= TValue.From<T>(FValue).AsVariant;
end;
procedure TMyValue<T>.SetValue(const AValue: T);
begin
if TEqualityComparer<T>.Default.Equals(AValue, FValue) then Exit;
FValue:= AValue;
//do something
end;
procedure TMyValue<T>.Assign(Source: TPersistent);
begin
if Source is TMyValue<T> then Value:= (Source as TMyValue<T>).Value
else inherited;
end;
Another way (tested XE10)
Var
old : variant;
val : TValue;
Begin
val := TValue.FromVariant(old);
End;

Delphi IS operator - Operator not applicable to this operand type

I guess this should be an easy one cause I must be doing something wrong.
this is my code, I'm trying to do a Strategy pattern in Delphi:
unit Pattern;
interface
type
TContext = class;
IStrategy = interface
function Move(c: TContext): integer;
end;
TStrategy1 = class(TInterfacedObject, IStrategy)
public
function Move(c: TContext): integer;
end;
TStrategy2 = class(TInterfacedObject, IStrategy)
public
function Move(c: TContext): integer;
end;
TContext = class
const
START = 5;
private
FStrategy: IStrategy;
public
FCounter: integer;
constructor Create;
function Algorithm(): integer;
procedure SwitchStrategy();
end;
implementation
{ TStrategy1 }
function TStrategy1.Move(c: TContext): integer;
begin
c.FCounter := c.FCounter + 1;
Result := c.FCounter;
end;
{ TStrategy2 }
function TStrategy2.Move(c: TContext): integer;
begin
c.FCounter := c.FCounter - 1;
Result := c.FCounter;
end;
{ TContext }
function TContext.Algorithm: integer;
begin
Result := FStrategy.Move(Self)
end;
constructor TContext.Create;
begin
FCounter := 5;
FStrategy := TStrategy1.Create();
end;
procedure TContext.SwitchStrategy;
begin
if FStrategy is TStrategy1 then
FStrategy := TStrategy2.Create()
else
FStrategy := TStrategy1.Create();
end;
end.
And the if FStrategy is TStrategy1 then is giving me: Operator not applicable to this operand type.
What am I doing wrong here cause this should work as I understand from a lot of Delphi language references?
You have omitted the GUID from your interface. is can't work without it.
Edit: On second glance, it still won't work. You can't use is to test an interface reference for its implementing object typein Delphi (well, not directly, anyway). You should change your design. For example, you could either alter the interface or add another interface to return a description of the implementation.
You could make this work by adding the IID/GUID as Craig states, and then changing SwitchStrategy to:
procedure TContext.SwitchStrategy;
begin
if (FStrategy as TObject) is TStrategy1 then
FStrategy := TStrategy2.Create()
else
FStrategy := TStrategy1.Create();
end;
This only works with more modern versions of Delphi. I think Delphi 2010 was where the ability to cast an interface to its implementing object was added.
However, I'd be inclined to avoid this solution and go for something like this:
type
IStrategy = interface
function Move(c: TContext): integer;
function Switch: IStrategy;
end;
TStrategy1 = class(TInterfacedObject, IStrategy)
public
function Move(c: TContext): integer;
function Switch: IStrategy;
end;
TStrategy2 = class(TInterfacedObject, IStrategy)
public
function Move(c: TContext): integer;
function Switch: IStrategy;
end;
function TStrategy1.Switch: IStrategy;
begin
Result := TStrategy2.Create;
end;
function TStrategy2.Switch: IStrategy;
begin
Result := TStrategy1.Create;
end;
procedure TContext.SwitchStrategy;
begin
FStrategy := FStrategy.Switch;
end;
When you find yourself asking an object what type it is, that's usually indicative of a design weakness.

Resources