This question already has answers here:
Two classes with two circular references
(2 answers)
Closed 9 years ago.
I would like to pass "self" as parameter to a method of another class (in a different unit). However the type of the first class is unknown in the second one, because I can't put the first unit into the uses section of the second unit. So I define the parameters type as pointer but when I try to call a method from the first class the Delphi 7 parser tells me that the classtyp is required.
So how should I solve this problem?
By making the class known in the implementaion part you can cast the given reference.
unit UnitY;
interface
uses Classes;
type
TTest=Class
Constructor Create(AUnKnowOne:TObject);
End;
implementation
uses UnitX;
{ TTest }
constructor TTest.Create(AUnKnowOne: TObject);
begin
if AUnKnowOne is TClassFromUnitX then
begin
TClassFromUnitX(AUnKnowOne).DoSomeThing;
end
else
begin
// ....
end;
end;
end.
I like the interface approach for this type of problem. Unless your units are very tightly coupled, in which case they should probably share a unit, interfaces are tidy ways of exchanging relevant parts of classes without having to have full knowledge of each type.
Consider :
unit UnitI;
interface
type
IDoSomething = Interface(IInterface)
function GetIsFoo : Boolean;
property isFoo : Boolean read GetIsFoo;
end;
implementation
end.
and
unit UnitA;
interface
uses UnitI;
type
TClassA = class(TInterfacedObject, IDoSomething)
private
Ffoo : boolean;
function GetIsFoo() : boolean;
public
property isFoo : boolean read GetIsFoo;
procedure DoBar;
constructor Create;
end;
implementation
uses UnitB;
constructor TClassA.Create;
begin
Ffoo := true;
end;
function TClassA.GetIsFoo() : boolean;
begin
result := Ffoo;
end;
procedure TClassA.DoBar;
var SomeClassB : TClassB;
begin
SomeClassB := TClassB.Create;
SomeClassB.DoIfFoo(self);
end;
end.
and notice that TClassB does not have to know anything about TClassA or the unit that contains it - it simply accepts any object that abides by the IDoSomething interface contract.
unit UnitB;
interface
uses UnitI;
type
TClassB = class(TObject)
private
Ffoobar : integer;
public
procedure DoIfFoo(bar : IDoSomething);
constructor Create;
end;
implementation
constructor TClassB.Create;
begin
Ffoobar := 3;
end;
procedure TClassB.DoIfFoo(bar : IDoSomething);
begin
if bar.isFoo then Ffoobar := 777;
end;
end.
Related
I am trying to create a class that implements an interface but I get these errors:
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface.QueryInterface
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface._AddRef
[dcc32 Error] dl_tPA_MailJournal.pas(10): E2291 Missing implementation of interface method IInterface._Release
[dcc32 Fatal Error] MainUnit.pas(8): F2063 Could not compile used unit 'dl_tPA_MailJournal.pas'
The code is:
unit dl_tPA_MailJournal;
interface
uses
Windows,
Generics.Collections,
SysUtils,
uInterfaces;
type
TtPA_MailJournal = class(TObject, ITable)
public
function GetanQId: integer;
procedure SetanQId(const Value: integer);
function GetadDate: TDateTime;
procedure SetadDate(const Value: TDateTime);
function toList: TList<string>;
constructor Create(aId : Integer; aDate : TDateTime);
private
property anQId : integer read GetanQId write SetanQId;
property adDate : TDateTime read GetadDate write SetadDate;
end;
implementation
{ TtPA_MailJournal }
constructor TtPA_MailJournal.Create(aId : Integer; aDate : TDateTime);
begin
SetanQId(aId);
SetadDate(aDate);
end;
function TtPA_MailJournal.GetadDate: TDateTime;
begin
Result := adDate;
end;
function TtPA_MailJournal.GetanQId: integer;
begin
Result := anQId ;
end;
procedure TtPA_MailJournal.SetadDate(const Value: TDateTime);
begin
adDate := Value;
end;
procedure TtPA_MailJournal.SetanQId(const Value: integer);
begin
anQId := Value;
end;
function TtPA_MailJournal.toList: TList<string>;
var
aListTable: TList<TtPA_MailJournal>;
aTable: TtPA_MailJournal;
aListString: TList<String>;
begin
aTable.Create(1,now);
aListTable.Add(aTable);
aTable.Create(2,now);
aListTable.Add(aTable);
aListString.Add(aListTable.ToString);
Result := aListString;
end;
end.
And the interface is:
unit uInterfaces;
interface
uses
Generics.Collections;
type
ITable = Interface
['{6CED8DCE-9CC7-491F-8D93-996BE8E4D388}']
function toList: TList<string>;
end;
implementation
end.
The problem is that you use TObject as the parent for your class. You should use TInterfacedObject instead.
In Delphi, every interface inherits from IInterface at therefore has, at least, the following 3 methods:
_AddRef
_Release
QueryInterface
You must implement these 3 methods, either by implementing them yourself or by inheriting from a base object that includes these methods.
Because you inherit from TObject, but you are not implementing these 3 methods, you get a compilation error. If you read the compiler error, you will see that it actually spells out this omission for you.
TInterfacedObject has already implemented these methods for you.
Other base objects that implement IInterface (aka IUnknown) are: TAggregatedObject and TContainedObject. However these are special purpose vehicles, only to be used if you really know what you're doing.
Change the definition of your class to
TTPA_MailJournal = class(TInterfacedObject, ITable)
And your code will compile.
See Delphi basics for more info.
suppose i have this definitions:
TMyClass1 = class
end;
TMyClass2 = class
end;
IModel<T : class> = interface
['{E8262D6C-DCAB-46AC-822E-EC369CF734F8}']
function List() : TObjectList<T>;
end;
IPresenter<T : class> = interface
['{98FB7751-D75A-4C51-B55A-0E5FE68BE213}']
function Retrieve() : TObjectList<T>;
end;
IView<T : class> = interface
['{59384CD6-30D6-4BD8-AB3D-7FCF4D1A8618}']
procedure AssignPresenter(APresenter : IPresenter<T>);
end;
TModel<T : class> = class(TInterfacedObject, IModel<T>)
public
function List() : TObjectList<T>; virtual; abstract;
end;
TPresenter<T : class> = class(TInterfacedObject, IPresenter<T>)
strict private
{ Private declarations }
FModel : IModel<T>;
FView : IView<T>;
public
constructor Create(AView : IView<T>);
function Retrieve() : TObjectList<T>; virtual; abstract;
end;
TModelClass1 = class(TModel<TMyClass1>);
TPresenterClass1 = class(TPresenter<TMyClass1>);
TModelClass2 = class(TModel<TMyClass2>);
TPresenterClass2 = class(TPresenter<TMyClass2>);
and i have this form that implements some of the things i defined:
TForm1 = class(TForm, IView<TMyClass1>, IView<TMyClass2>)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FPresenter1 : IPresenter<TMyClass1>;
FPresenter2 : IPresenter<TMyClass2>;
procedure AssignPresenter(APresenter : IPresenter<TMyClass1>); overload;
procedure AssignPresenter(APresenter : IPresenter<TMyClass2>); overload;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TPresenterClass1.Create((Self as IView<TMyClass1>));
TPresenterClass2.Create((Self as IView<TMyClass2>));
end;
procedure TForm1.AssignPresenter(APresenter: IPresenter<TMyClass1>);
begin
Self.FPresenter1 := APresenter;
end;
procedure TForm1.AssignPresenter(APresenter: IPresenter<TMyClass2>);
begin
Self.FPresenter2 := APresenter;
end;
so the problem here is that delphi can't figure out what method to call, in this example, only the AssignPresenter(APresenter: IPresenter<TMyClass2>) is called in both cases, so probably im missing something here but i can't figure out atm.
thx in advance.
This is probably a duplicate but I cannot find it right now.
The problem is that the as operator for interfaces is not very compatible with generics. The as operator relies on the interface GUID. The interface is found by querying for an interface with matching GUID. And GUIDs don't fit at all well with generic instantiation.
Now let's look at your code.
TPresenterClass1.Create((Self as IView<TMyClass1>));
TPresenterClass2.Create((Self as IView<TMyClass2>));
The problem is that IView<TMyClass1> and IView<TMyClass2> have the same GUID:
type
IView<T : class> = interface
['{59384CD6-30D6-4BD8-AB3D-7FCF4D1A8618}']
procedure AssignPresenter(APresenter : IPresenter<T>);
end;
So both IView<TMyClass1> and IView<TMyClass2> share the same GUID, and when you query using as, the same interface will be returned irrespective of whether or not you asked for IView<TMyClass1> or IView<TMyClass2>.
So, the bottom line here is that as is rendered next to useless with generic interfaces as soon as an object implements ISomeInterface<T> twice with different T.
Embarcadero really should implement as in a manner that supports generics. I wouldn't hold your breath though.
You will need to find a different way to solve your problem.
No need to use as when you have the object implementing the interface - just write:
procedure TForm1.FormCreate(Sender: TObject);
begin
TPresenterClass1.Create(Self);
TPresenterClass2.Create(Self);
end;
The compiler figures this out properly. When you are using as it does a Supports call which fails for the reason explained by David already.
As an addition to David Heffernan's answer.
One way you could work around the problem would be to give a unique GUID to all the generic declarations you use:
IViewMyclass1 = interface(IView<TMyClass1>)
['{1A0F941F-BAB1-4723-A6C1-27036DF5D344}']
end;
IViewMyclass2 = interface(IView<TMyClass2>)
['{0C61A23A-DC50-43B0-97C9-8B0013DDC193}']
end;
Redefine the view's declaration.
TForm4 = class(TForm, IViewMyclass1, IViewMyclass2)
procedure TForm1.FormCreate(Sender: TObject);
begin
TPresenterClass1.Create((Self as IViewMyclass1));
TPresenterClass2.Create((Self as IViewMyclass2));
end;
And then the right overload is called.
Disclaimers:
This is the result of On-The-Fly testing (XE4). Reliability is unknown.
I'm aware this isn't very practical.
It's probably not something that should be extensively used as it isn't obvious where the code would fail.
This question already has answers here:
Delphi interface inheritance: Why can't I access ancestor interface's members?
(4 answers)
Closed 8 years ago.
I have to write some interfaces that will be used to implement a number of classes. Some of them will have a "basic" behaviour, others whill have some "advanced" features.
I think the best way is to declare a "basic" interface and an "advanced" child interface.
I'm also tryng to keep a loose coupling between objects and interfaces.
Now I'm running into this problem:
When I create an "Advanced" object (implementing the Child interface), I expect it also implements the Parent interface, but neither "getInterface" nor "Supports" seem to agree with me.
Here is a sample code:
type
IParent = interface
['{684895A1-66A5-4E9F-A509-FCF739F3F227}']
function ParentFunction: String;
end;
IChild = interface(IParent)
['{B785591A-E816-4D90-BA01-1FFF865D312A}']
function ChildFunction: String;
end;
TMyClass = class(TInterfacedObject, IChild)
public
function ParentFunction: String;
function ChildFunction: String;
end;
function TMyClass.ChildFunction: String;
begin
Result := 'ChildFunction';
end;
function TMyClass.ParentFunction: String;
begin
Result := 'ParentFunction';
end;
var
Obj: TMyClass;
ParentObj: IParent;
ChildObj: IChild;
begin
Obj := TMyClass.Create;
ChildObj := Obj;
WriteLn(Format('%s as IChild: %s', [Obj.ClassName, ChildObj.ChildFunction]));
WriteLn(Format('%s as IChild: %s', [Obj.ClassName, ChildObj.ParentFunction]));
if (Obj.GetInterface(IParent, ParentObj)) then
WriteLn(Format('GetInterface: %s as IParent: %s', [Obj.ClassName, ParentObj.ParentFunction]))
else
WriteLn(Format('GetInterface: %s DOES NOT implement IParent', [Obj.ClassName])); // <-- Why?
ParentObj := ChildObj;
WriteLn(Format('%s as IParent: %s', [Obj.ClassName, ParentObj.ParentFunction]));
if (Supports(Obj, IParent)) then
WriteLn(Format('Supports: %s Supports IParent', [Obj.ClassName]))
else
WriteLn(Format('Supports: %s DOES NOT Support IParent', [Obj.ClassName])); // <-- Why?
end.
and here is the result:
TMyClass as IChild: ChildFunction
TMyClass as IChild: ParentFunction
GetInterface: TMyClass DOES NOT implement IParent
TMyClass as IParent: ParentFunction
Supports: TMyClass DOES NOT Support IParent
How can I, for example, test if an object implements IParent OR A DESCENDANT of it?
Thank you
The reason why TMyClass doesn't support IParent is because you didn't say it should do so. That is just as designed. If you want TMyClass to support IParent, just say so in the declaration:
TMyClass = class(TInterfacedObject, IParent, IChild)
I'm using a very large delphi third party library without source code, this library has several classes with abstract methods. I need to determine when an abtract method is implemented by a Descendant class in runtime to avoid the EAbstractError: Abstract Error and shows a custom message to the user or use another class instead.
for example in this code I want to check in runtime if the MyAbstractMethod is implemented.
type
TMyBaseClass = class
public
procedure MyAbstractMethod; virtual; abstract;
end;
TDescendantBase = class(TMyBaseClass)
public
end;
TChild = class(TDescendantBase)
public
procedure MyAbstractMethod; override;
end;
TChild2 = class(TDescendantBase)
end;
How I can determine if an abstract method is implemented in a Descendant class in runtime?
you can use the Rtti, the GetDeclaredMethods function get a list of all the methods that are declared in the reflected (current) type. So you can check if the method is present in the list returned by this function.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
for m in TRttiContext.Create.GetType(AClass.ClassInfo).GetDeclaredMethods do
begin
Result := CompareText(m.Name, MethodName)=0;
if Result then
break;
end;
end;
or you can compare the Parent.Name property of the TRttiMethod and check if match with the current class name.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
m:=TRttiContext.Create.GetType(AClass.ClassInfo).GetMethod(MethodName);
if m<>nil then
Result:=CompareText(AClass.ClassName,m.Parent.Name)=0;
end;
function ImplementsAbstractMethod(AObj: TMyBaseClass): Boolean;
type
TAbstractMethod = procedure of object;
var
BaseClass: TClass;
BaseImpl, Impl: TAbstractMethod;
begin
BaseClass := TMyBaseClass;
BaseImpl := TMyBaseClass(#BaseClass).MyAbstractMethod;
Impl := AObj.MyAbstractMethod;
Result := TMethod(Impl).Code <> TMethod(BaseImpl).Code;
end;
Look at the implementation of the 32-bit version of the TStream.Seek() method in the VCL source code (in Classes.pas). It performs a check to make sure the 64-bit version of Seek() has been overridden before calling it. It doesn't involve TRttiContext lookups to do that, just a simple loop through its Parent/Child VTable entries, similar to how Zoƫ's answer shows.
With Delphi 2009 Enterprise I created code for the GoF Visitor Pattern in the model view, and separated the code in two units: one for the domain model classes, one for the visitor (because I might need other units for different visitor implementations, everything in one unit? 'Big ball of mud' ahead!).
unit VisitorUnit;
interface
uses
ConcreteElementUnit;
type
IVisitor = interface;
IElement = interface
procedure Accept(AVisitor :IVisitor);
end;
IVisitor = interface
procedure VisitTConcreteElement(AElement :TConcreteElement);
end;
TConcreteVisitor = class(TInterfacedObject, IVisitor)
public
procedure VisitTConcreteElement(AElement :TConcreteElement);
end;
implementation
procedure TConcreteVisitor.VisitTConcreteElement(AElement :TConcreteElement);
begin
{ provide implementation here }
end;
end.
and the second unit for the business model classes
unit ConcreteElementUnit;
interface
uses
VisitorUnit;
type
TConcreteElement = class(TInterfacedObject, IElement)
public
procedure Accept(AVisitor :IVisitor); virtual;
end;
Class1 = class(TConcreteElement)
public
procedure Accept(AVisitor :IVisitor);
end;
implementation
{ Class1 }
procedure Class1.Accept(AVisitor: IVisitor);
begin
AVisitor.VisitTConcreteElement(Self);
end;
end.
See the problem? A circular unit reference. Is there an elegant solution? I guess it requires "n+1" additional units with base interface / base class definitions to avoid the CR problem, and tricks like hard casts?
I use the following scheme to implement a flexible visitor pattern:
Declaration of base visitor types
unit uVisitorTypes;
type
IVisited = interface
{ GUID }
procedure Accept(Visitor: IInterface);
end;
IVisitor = interface
{ GUID }
procedure Visit(Instance: IInterface);
end;
TVisitor = class(..., IVisitor)
procedure Visit(Instance: IInterface);
end;
procedure TVisitor.Visit(Instance: IInterface);
var
visited: IVisited;
begin
if Supports(Instance, IVisited, visited) then
visited.Accept(Self)
else
// raise exception or handle error elsewise
end;
The unit for of the element class
unit uElement;
type
TElement = class(..., IVisited)
procedure Accept(Visitor: IInterface);
end;
// declare the visitor interface next to the class-to-be-visited declaration
IElementVisitor = interface
{ GUID }
procedure VisitElement(Instance: TElement);
end;
procedure TElement.Accept(Visitor: IInterface);
var
elementVisitor: IElementVisitor;
begin
if Supports(Visitor, IElementVisitor, elementVisitor) then
elementVisitor.VisitElement(Self)
else
// if override call inherited, handle error or simply ignore
end;
The actual visitor implementation
unit MyVisitorImpl;
uses
uVisitorTypes, uElement;
type
TMyVisitor = class(TVisitor, IElementVisitor)
procedure VisitElement(Instance: TElement);
end;
procedure TMyVisitor.VisitElement(Instance: TElement);
begin
// Do whatever you want with Instance
end;
Calling the visitor
uses
uElement, uMyElementVisitor;
var
visitor: TMyVisitor;
element: TElement;
begin
// get hands on some element
visitor := TMyVisitor.Create;
try
visitor.Visit(element);
finally
visitor.Free;
end;
end;
Why not define IVisitor
IVisitor = interface
procedure VisitElement(AElement :IElement);
end;
then TConcreteElement in its own unit :
unit ConcreteElementUnit;
interface
uses
VisitorUnit;
type
TConcreteElement = class(TInterfacedObject, IElement)
public
procedure Accept(AVisitor :IVisitor); virtual;
end;
Class1 = class(TConcreteElement)
public
procedure Accept(AVisitor :IVisitor);
end;
implementation
{ Class1 }
procedure Class1.Accept(AVisitor: IVisitor);
begin
AVisitor.VisitElement(Self);
end;
end.
That way you are not mixing class and interface references (always a bad idea)
The following implementation using generic type on Visitor interface to solve the circular reference issue of Visitor pattern:
Visitor.Intf.pas:
unit Visitor.Intf;
interface
type
IVisitor<T> = interface
procedure Visit_Element(o: T);
end;
implementation
end.
Element.pas:
unit Element;
interface
uses Visitor.Intf;
type
TElement = class
procedure Accept(const V: IVisitor<TElement>);
end;
implementation
procedure TElement.Accept(const V: IVisitor<TElement>);
begin
V.Visit_Element(Self);
end;
end.
Visitor.Concrete.pas:
unit Visitor.Concrete;
interface
uses Element, Visitor.Intf;
type
TConcreteVisitor = class(TInterfacedObject, IVisitor<TElement>)
protected
procedure Visit_Element(o: TElement);
end;
implementation
procedure TConcreteVisitor.Visit_Element(o: TElement);
begin
// write implementation here
end;
end.
Using the TElement and TConcreteVisitor class:
var E: TElement;
begin
E := TElement.Create;
E.Accept(TConcreteVisitor.Create as IVisitor<TElement>);
E.Free;
end;
The decleration of TConcreteElement shoud be in VisitorUnit (or a third unit)
or better
The IVisitator should be changed to:
IVisitor = interface
procedure VisitTConcreteElement(AElement :IElement);
end;