Board index » delphi » Interface question

Interface question


2003-10-09 01:11:20 PM
delphi174
Hello --
consider the following console app:
program DelegationTest;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
Type
I1 = interface(IInterface)
Function F1: real;
Function F2: real;
end;
I2 = interface(I1)
Function F3: real;
end;
T1 = class(TInterfacedObject)
Function F3: real;
end;
T2 = class(T1, I1, I2)
private
FI1: I1;
FI2: I2;
public
Constructor Create(Inter1: I1);
property Int1: I1 read FI1 implements I1;
// property Int2: I2 read FI2 implements I2;
end;
(all methods are implemented as usual)
...
begin
...
end.
I get 2 complier errors:
Undeclared identifier: 'F1'
Undeclared identifier: 'F2'
both pointing at the end of T2 declaration.
if I uncomment the second property (Int2), the errors disappear.
Alternatively, if I delegate I2 to the interface-type property, and
implement I1 in T2 (instead of delegating it) no errors come up.
My question is:
Why do I get these errors?
Your help would be appreciated,
Mike Umorin
 
 

Re:Interface question

mike writes:
| hi --
| consider the following console app:
|
| program DelegationTest;
|
| {$APPTYPE CONSOLE}
|
| uses
| SysUtils, Classes;
|
|
| Type
|
| I1 = interface(IInterface)
| Function F1: real;
| Function F2: real;
| end;
|
| I2 = interface(I1)
| Function F3: real;
| end;
|
|
| T1 = class(TInterfacedObject)
| Function F3: real;
| end;
|
|
| T2 = class(T1, I1, I2)
| private
| FI1: I1;
| FI2: I2;
| public
| Constructor Create(Inter1: I1);
| property Int1: I1 read FI1 implements I1;
| // property Int2: I2 read FI2 implements I2;
| end;
|
| (all methods are implemented as usual)
| ...
| begin
| ...
| end.
|
| I get 2 complier errors:
|
| Undeclared identifier: 'F1'
| Undeclared identifier: 'F2'
|
| both pointing at the end of T2 declaration.
|
| if I uncomment the second property (Int2), the errors disappear.
| Alternatively, if I delegate I2 to the interface-type property, and
| implement I1 in T2 (instead of delegating it) no errors come up.
What you are doing is to derive I2 from I1 but provide implementors for both
in a class that already implements the additional F3 from I2.
This sounds confusing and it is not too much of a surprise that the compiler
can't handle it.
Try removing the derivation in I2 and see what I mean. It is difiicult to
provide a 'correct' answer to how these interfaces should be implemented
from the code given as the intent of the design is not apparent, but there
is a funny smell about the design you are hinting at :-)
BTW I hope you are not using Real as a data type, it was deprecated a long
time ago.
Joanna
 

Re:Interface question

Joanna Carter writes:
Quote

This sounds confusing and it is not too much of a surprise that the
compiler can not handle it.
If I want T2 to implement both interfaces (which I do) I need to list them
both in the ancestor list. Then, I need to implement both of them. Which I
do. But, I want to implement I1 through an incapsulated oblect which would
be assigned to I1 field. that is the design.
Quote

Try removing the derivation in I2 and see what I mean.
what do you mean by that (i.e. "the derivation in I2")?
thanks for reply,
Mike.
 

Re:Interface question

mike writes:
| If I want T2 to implement both interfaces (which I do) I need to list them
| both in the ancestor list. Then, I need to implement both of them. Which I
| do. But, I want to implement I1 through an incapsulated oblect which would
| be assigned to I1 field. that is the design.
T2 already implements I2 due to its derivation from T1 which contains F3.
You do not have to add another implementation clause for I2, that is what
the compiler is complaining about.
type
I1 = interface(IInterface)
function F1: real;
function F2: real;
end;
I2 = interface(I1)
function F3: real;
end;
T1 = class(TInterfacedObject)
protected
function F3: real;
end;
T2 = class(T1, I1, I2)
private
fI1: I1;
public
constructor Create(Inter1: I1);
property Int1: I1
read FI1
implements I1;
end;
| what do you mean by that (i.e. "the derivation in I2")?
Unless I2 is intended to possess all the attributes and operations of I1 as
well as its own, then I2 should not derive from I1. You can still implement
both interfaces in one class, just alter the declaration of I2.
I1 = interface(IInterface)
function F1: real;
function F2: real;
end;
I2 = interface
function F3: real;
end;
Joanna
 

Re:Interface question

Joanna Carter writes:
Quote
T2 already implements I2 due to its derivation from T1 which contains F3.
I am aware of that.
Quote
You do not have to add another implementation clause for I2, that is what
I do not have "another implementation clause for I2" - it is commented out.
Quote
the compiler is complaining about.
The compiler is complaining about functions declared in I1, not I2
let me quote my original post: "if I uncomment the second property (Int2),
the errors disappear." and I'd certainly delete decalration of F3 in T1
in such a case.
That's the whole point: I can delegate the descendant or both interfaces,
but not the ancestor interface alone. I experimented more with the program
and I think this is the case for every pair of interfaces in class ancestor
list linked by inheritance. It looks like an undocumented feature. Should I
report it? and where?
Quote

Unless I2 is intended to possess all the attributes and operations of I1
as well as its own, then I2 should not derive from I1.
Unfortunately that IS what I have to do. Here is the suituation:
an object (A) interacts with an environment (E). The environment can be
represented by different objects, which A cares nothing about. So,
Interfaces are the way to go. Now, E can be represented by an agglomeration
of objects. There are several levels of abstraction E must provide, hence
several inheriting interfaces. Now, some of the higher abstract features
are provided by one object in a agglomerate, other features - by other
objects, and, finally, the lower level abstract feature are provided by the
container object itself. So, it makes sense to delegate features of the
real objects to classes representing those agglomerated objects in the
program, and implement features of the real container in its abstraction as
well, which leads to the situation presented in the original post.
Thanks for bearing with me, Joanna.
Mike.
 

Re:Interface question

In article <XXXX@XXXXX.COM>, XXXX@XXXXX.COM says...
Quote
Hello --
consider the following console app:
I1 = interface(IInterface)
Function F1: real;
Function F2: real;
end;
I2 = interface(I1)
Function F3: real;
end;
Okay, so I2 inherits from I1, meaning that it has all three functions:
F1, F2 and F3.
Quote
T1 = class(TInterfacedObject)
Function F3: real;
end;
T2 = class(T1, I1, I2)
private
FI1: I1;
FI2: I2;
public
Constructor Create(Inter1: I1);
property Int1: I1 read FI1 implements I1;
// property Int2: I2 read FI2 implements I2;
end;
This looks like playing on the edge of the language definition :)
Quote
(all methods are implemented as usual)
...
begin
...
end.

I get 2 complier errors:
[Robot drone voice] You have two errors to comply...
Quote

Undeclared identifier: 'F1'
Undeclared identifier: 'F2'

both pointing at the end of T2 declaration.

if I uncomment the second property (Int2), the errors disappear.
Alternatively, if I delegate I2 to the interface-type property, and
implement I1 in T2 (instead of delegating it) no errors come up.

My question is:
Why do I get these errors?
From what I understand of the mapping process that happens when looking
for items to hook up to an interface.
For I1, Delphi will take a look first for a delegate. It finds one in the
form of your Int1 property.
For I2, Delphi first takes a look for a delegate. It does not find one,
so it looks for a delegate for a derived interface. It does not find one
(I2 inherits from I1 instead of the other way around), so it falls back
on the default mapping the methods of I2 - it therefore looks for
functions F1, F2 and F3.
You have an F3 declared in the base class, so no problem mapping that. F1
and F2, of course, cannot be found.
This is all If-I-Recall-Correctly.
Delphi will not map one part of an interface (->function F3) and delegate
the rest to an interface property (->property Int1).
Delphi will, however, delegate an interface to a property that contains a
derived interface (since it is 'compatible' and contains all the
methods).
(I do not know if Delphi will delegate an interface to a property that
contains an interface with the same methods, but that is NOT a derivative
- I'd doubt it, but I wouldn't be surprised :)
Delphi will also (from my testing), however, map one part of an interface
and delegate the rest to an OBJECT property (e.g. if you had FI1:
TSomeDelegate; property Int1: TSomeDelegate read FI1 implements I1).
And, of course, you can not map two interfaces to the same property. (At
least I couldn't in D5 :)
I can only gather that the mapping-to-method and mapping-to-object
operations in Delphi are of one type, and mapping-to-interface operations
are of a different type (I would bet it uses some shortcut to get around
the double dereferencing required), and you simply can not mix them.
Quote
Your help would be appreciated,
If I may ask, what on earth is this for? :)
By the way - be careful of reference counting when delegating to an -
interface- property (the _AddRef and _Release will map to the delegated
interface). Joanna has a set way of instantiating objects like this that
she's had some good luck with (hint: don't instantiate it as an I2 and
cast it/pass it around as an I1).
Have you taken a look at the InterfacesExplorer demo in the programming
section of my web site? There are some examples relating to interface
delegation there.
Quote
Mike Umorin
Best wishes :)
-- Ritchie Annand
Senior Software Architect
Bus: www.malibugroup.com
Home: nimble.nimblebrain.net
Wiki: wiki.nimblebrain.net
 

Re:Interface question

mike writes:
| that is the whole point: I can delegate the descendant or both interfaces,
| but not the ancestor interface alone. I experimented more with the program
| and I think this is the case for every pair of interfaces in class
| ancestor list linked by inheritance. It looks like an undocumented
| feature. Should I report it? and where?
| T1 = class(TInterfacedObject)
| Function F3: real;
| end;
| T2 = class(T1, I1, I2)
| private
| FI1: I1;
| FI2: I2;
| public
| Constructor Create(Inter1: I1);
| property Int1: I1 read FI1 implements I1;
| // property Int2: I2 read FI2 implements I2;
You have to create an object into FI1 to look after the delegated methods of
I1. What class are you creating an instance of to look after the methods of
I1?
T1 has the method F3 from I2 but you cannot call QueryInterface on T1 to get
a reference to I2 because I2 is not mentioned in the declaration; therefore
T2 does not support F3 of I2 until you comment out Int2.
I would guess that why you get messages about F1 and F2 is because the
compiler can not find support for one method of I2 and assumes that any class
that can not support a derived method can not support an ancestor either. This
could well be down to a shortcut in a highly optimised compiler.
| an object (A) interacts with an environment (E). The environment can be
| represented by different objects, which A cares nothing about. So,
| Interfaces are the way to go. Now, E can be represented by an
| agglomeration of objects. There are several levels of abstraction E must
| provide, hence several inheriting interfaces. Now, some of the higher
| abstract features are provided by one object in a agglomerate, other
| features - by other objects, and, finally, the lower level abstract
| feature are provided by the container object itself. So, it makes sense
| to delegate features of the real objects to classes representing those
| agglomerated objects in the program, and implement features of the real
| container in its abstraction as well, which leads to the situation
| presented in the original post.
Thinking about what Ritchie says in his post and from what I can make of
this scenario, your code design is not very sound and could possibly do with
revising to a more 'normal' model:
type
IBaseEnvironment = interface
function F1: Double;
function F2: Double;
end;
IFeature = interface
function FF1: Double;
end;
ISpecialEnvironment = interface(IBaseEnvironment)
function FS1: Double;
end;
TBaseEnvironment = class(TInterfacedObject, IBaseEnvironment)
function F1: Double;
function F2: Double;
end;
TFeature = class(TInterfacedObject, IFeature)
private
function FF1: Double;
end;
TSpecialEnvironment = class(TBaseEnvironment, ISpecialEnvironment,
IFeature)
private
fFeature: IFeature;
// IFeature
property Feature: IFeature
read fFeature
implements IFeature;
function FF1: Double;
// ISpecialEnvironment
function FS1: Double;
end;
You said you wanted to have the base environment as a delegated object, but
if it is going to be an integral part of every specialised environment, then
why not derive the specialised class from the base class? You will still
have separated out the behaviour.
The above code compiles and also does not require that you create an
instance of the base environment to pass into the constructor of the special
environment as that is taken care simply by calling the 'inherited'
constructor.
Ritchies concerns about reference counting are very valid, but only if you
allow any of the delegates to hold a reference to the containing class.
Joanna
 

Re:Interface question

Joanna and Ritchie --
thank you very much!
this problem made me look into the abstractions of my model and I had to
revise it to something close to your proposal, Joanna. A crash-course in
interface mapping by Ritchie will be very helpful in implementation of my
environments. Ah, and I derive all my classes from a base class where I
have disabled reference counting because I need my objects beyond merely
providing environment for other objects.
Thanks again,
Mike.
 

Re:Interface question

another round..., more questions
Quote
You have to create an object into FI1 to look after the delegated methods
of I1. What class are you creating an instance of to look after the
methods of I1?

there is a class to take care of it, I can call it T3 and declare F1 and F2
there.
Quote

You said you wanted to have the base environment as a delegated object,
but if it is going to be an integral part of every specialised
environment, then why not derive the specialised class from the base
class? You will still have separated out the behaviour.

because for different objects representing environment it is different: same
set of functions can belong to either a single object or can be spread out
across several objects in an agglomerate. I could delegate some functions
to a class property, but if I have many objects I can not do that: only one
property can be used for interface delegation according to the language
spec.
I wonder, Joanna, if there is any way to reference the interfaces provided
by TSpecialEnvironment in a single variable (maybe a pointer, and then
recast into different interfaces as needed), because conceptually it is a
single "environment", just different features of it come from different
objects.
Thanks.
 

Re:Interface question

mike writes:
| there is a class to take care of it, I can call it T3 and declare F1 and
| F2 there.
Be warned, it is not possible to create a container class that does nothing
other than hold aggregated delegates. I know someone who tried this and it
was not a pretty sight!!
ICoreBehaviour = interface
...
end;
IOtherBehaviour = interface
...
end;
TCoreClass = class(TInterfacedObject, ICoreBehaviour)
...
end;
TContainerClass = class(TInterfacedObject, ICoreBehaviour, IOtherBehaviour)
...
property Core: ICoreBehaviour ... implements... // create TCoreClass
property Other IOtherBehaviour ... implements...
end;
If I now call:
var
MyCore: ICoreBehaviour;
begin
MyCore := TContainerClass.Create;
...
end;
... what will happen is that the MyCore var will only contain an instance of
TCoreClass.
Unless you pass a reference to TContainerClass to TCoreClass and fiddle with
TCoreClass.QueryInterface, there is no way for a cast from ICoreBehaviour to
get to any of the other interfaces implemented in TContainerClass.
| because for different objects representing environment it is different:
| same set of functions can belong to either a single object or can be
| spread out across several objects in an agglomerate. I could delegate
| some functions to a class property, but if I have many objects I can not do
| that: only one property can be used for interface delegation according to
| the language spec.
I think I'd redesign the base interface by factoring out the
'sub-behaviours'
| I wonder, Joanna, if there is any way to reference the interfaces provided
| by TSpecialEnvironment in a single variable (maybe a pointer, and then
| recast into different interfaces as needed), because conceptually it is a
| single "environment", just different features of it come from different
| objects.
As long as the implementing hierarchy allows QueryInterface from one object
to another to work correctly, any interface type can be cast to any other
interface type.
e.g. Observer Pattern
IObserver = interface
procedure Update(const Subject: IInterface);
end;
ISubject = interface
...
procedure Notify;
end;
TSubject = class(TInterfacedObject, ISubject)
private
fController: Pointer;
fObservers: IObserverList;
...
procedure Notify;
public
constructor Create(const Controller: IInterface)
end;
IAccount = interface
...
procedure ChangeBalance;
end;
TAccount = class(TInterfacedObject, IAccount, ISubject)
private
fSubject: ISubject;
property Subject: ISubject
read fSubject
implements ISubject;
...
procedure ChangeBalance;
public
constructor Create;
end;
implementation
// TSubject
constructor TSubject.Create(const Controller: IInterface)
begin
inherited Create;
fController := Pointer(Controller);
end;
procedure TSubject.Notify;
var
iter: IObserverIterator;
begin
if fObserver <>nil then
begin
iter := fObservers.GetIterator;
while iter.Next do
iter.CurrentItem.Update(IInterface(fController));
end;
end;
// TAccount
constructor TAccount.Create;
begin
inherited Create;
fSubject := TSubject.Create(self);
end;
procedure TAccount.ChangeBalance;
begin
// fiddle the figures
fSubject.Notify;
end;
If you see what I mean??
Joanna
 

Re:Interface question

Quote

Unless you pass a reference to TContainerClass to TCoreClass and fiddle
with TCoreClass.QueryInterface, there is no way for a cast from
ICoreBehaviour to get to any of the other interfaces implemented in
TContainerClass.
So, correct me if I am wrong, if a class C implements interfaces A and B. If
I cast C as A and pass it to a variable (say a Pointer); then there is no
way of extracting B if B is not ancestor of A. The only way I could "cheat"
is the have an object implementing A to have a reference to C (or B). And
this holds whether there is delegation involved or not?
Out of curiosity: how to "fiddle" with the QueryInterface to achieve that?
Quote

I think I'd redesign the base interface by factoring out the
'sub-behaviours'
and reference them (sub-behaviours) separatly through separate interface
variables even if the underlying implementing object is the same?
Quote

As long as the implementing hierarchy allows QueryInterface from one
object to another to work correctly, any interface type can be cast to any
other interface type.

...

If you see what I mean??
not really. Do you mean that an interface (TAccount as ISubject) can be
cast as its ancestor (IInterface), then as pointer, that as ancestor
(IInterface) again? Can I cast it as IAccount later? And what would it take
for the QueryInterface to do the job?
Thanks for educating me
Mike.
 

Re:Interface question

mike writes:
| So, correct me if I am wrong, if a class C implements interfaces A and B.
| If I cast C as A and pass it to a variable (say a Pointer); then there is
| no way of extracting B if B is not ancestor of A. The only way I could
| "cheat" is the have an object implementing A to have a reference to C (or
| B). And this holds whether there is delegation involved or not?
| Out of curiosity: how to "fiddle" with the QueryInterface to achieve that?
Casting one interface to another doesn't rely on those interfaces deriving
from each other, we are not dealing with classes.
A class can implement as many disparate interfaces as it wants without any
of those interfaces being connected in any kind of hierarchy.
The standard implementation of QueryInterface found in TInterfacedObject can
find out which interfaces are implemented from the list of interfaces that
were declared for that class and its ancestors.
| and reference them (sub-behaviours) separatly through separate interface
| variables even if the underlying implementing object is the same?
That's how interfaces work best; one class, many behaviours.
| not really. Do you mean that an interface (TAccount as ISubject) can be
| cast as its ancestor (IInterface), then as pointer, that as ancestor
| (IInterface) again? Can I cast it as IAccount later? And what would it
| take for the QueryInterface to do the job?
See my first comment. In the Account example, TAccount can be cast to
IInterface, IAccount and ISubject, but you cannot cast from ISubject back to
IAccount because TSubject does not know which type in which it is held as an
aggregate member.
The reference to the Controller is needed in this example so that the
Subject delegate can pass the real object that is being observed to the
observers rather than itself.
The trick of storing the Controller as a pointer is to avoid incrementing
the reference count on the Account, thereby creating a situation where the
Account has a reference to the Subject and the Subject has a reference to
the Account; this stops either reference being released and therefore you
would have a memory leak.
Does that get things straight yet?
Joanna
 

Re:Interface question

Joanna Carter writes:
Quote
See my first comment. In the Account example, TAccount can be cast to
IInterface, IAccount and ISubject, but you cannot cast from ISubject back
to IAccount because TSubject does not know which type in which it is held
as an aggregate member.
However, had we implemented ISubject without delegation, then we could cast
(ISubject as IAccount), right?
Quote

The reference to the Controller is needed in this example so that the
Subject delegate can pass the real object that is being observed to the
observers rather than itself.
i.e. to pass IInterface implementation in TAccount rather then in TSubject,
right?
Quote

The trick of storing the Controller as a pointer is to avoid incrementing
the reference count on the Account, thereby creating a situation where the
Account has a reference to the Subject and the Subject has a reference to
the Account; this stops either reference being released and therefore you
would have a memory leak.
but if we rewrite _AddRef and _Release to := -1 then we may not care about
reference counting but then we should keep object references throughout the
program to manualy destroy the objects when needed, right?
Quote

Does that get things straight yet?
I see the light in the end of the tunnel and hope that it is not from the
approaching train... Thanks!
Mike
 

Re:Interface question

mike writes:
| However, had we implemented ISubject without delegation, then we could
| cast (ISubject as IAccount), right?
Yes
| i.e. to pass IInterface implementation in TAccount rather then in
| TSubject, right?
I think what you are saying is right; as long as you pass an IInterface
reference of the Controller to the Observers, then it can be cast back to
its original type (ICustomer)
| but if we rewrite _AddRef and _Release to := -1 then we may not care about
| reference counting but then we should keep object references throughout
| the program to manualy destroy the objects when needed, right?
After many experiments with delegated interfaces, I'd definitely say
that my method is the simplest and most reliable. Do *not* mix interface and
object references, full stop, period, etc....
| I see the light in the end of the tunnel and hope that it is not from the
| approaching train... Thanks!
WhoooHooo :-)
Joanna
 

Re:Interface question

Jeffrey A. Wormsley writes:
Quote
I've not used interfaces before (other than copy/pasted code to
create shell extensions), but I have a situation now where I think
they are appropriate.

I have two different was of doing the same thing. Both are already
coded as classes, and the public portions are the same, but they
currently are in different applications. I want to be able to use
either (but not both) in the same application. At program start up,
an instance is created and a discovery process is ran that returns a
list of possible values, and the user selects one and the object then
uses that value until the program exits, or they go to a config menu
and select another value.

Non-interface type code would mean I had to create both and build a
super- object that contained one instance of each class, built a list
that consisted of both lists, and for each public call, checked to
see which instance was selected and make the call to the appropriate
one. That seems messy.

With an interface, it seems I'd be able to code the two classes
to both implement the same interface, then have a class that created
one or the other instance and called the public items directly.

My questions are:

1. Does the above make sense.
Most definitely.
Quote
2. How do I deal with the lists? I suppose I still have to create
instances of both class to get the values. How should I handle that?
Will this work?
Not sure what you want to do.
Quote
3. Does this need interfaces at all, or do I just need to base both
classes off of an ancestor, and make the FMyThing of the ancestor
type?
If you use interfaces, you are not forced to do that. Code cna use any
class that exposes the interface. That gioves you a lot more freedom.
Ok, for two classes, it may seem a bit overdone, but using interfaces
makes it more extensible.
BTW, this looks like a nice question for
borland.public.delphi.oodesign, so I*v eset followup to that group.
People there are more versed in this than I am.
--
Rudy Velthuis [TeamB]
"So I was getting into my car, and this bloke says to me "Can you
give me a lift?" I said "Sure, you look great, the world's your
oyster, go for it.'" -- Tommy Cooper