In article <40cc1...@newsgroups.borland.com>, noki...@yahoo.com says...
> Hi everybody,
Hi Robert :)
> I am seeking some advice from some more experienced users of Delphi
> interfaces regarding the rules I should follow whenever I develop my domain
> objects with the use of interfaces. I am aware of only one rule:
> "Either always use interface references, or object references, but never mix
> the two of them"
That's a very good rule of thumb. You -can- break the rule if you
understand exactly what's going on, but by and large it's a bad idea.
-- 8< --
Here's a quick breakdown of why:
* Using interface references inserts (Object)._AddRef calls during
assignment and non-const parameter calls (this usually isn't a problem)
* Using interface references inserts (Object)._Release calls during re-
assignment, at the end of methods or while freeing an object they're
contained within. - This is the one that causes half of the problems,
because if you manually .Free an object that has an interface reference
on it, you'll get an (Invalid pointer)._Release being called.
* If you assign the result of a constructor to an object reference (X :
TMyObject; X := TMyObject.Create), it starts with a RefCount of 0 - if
you assign it to an interface reference (X : IMyInterface; X :=
TMyObject.Create;), it starts with a RefCount of 1. So, if you do the
object reference first, the first call you pass it into as an interface
reference will go (Object)._AddRef, (do stuff), (Object)._Release, which
will drop the RefCount down to 0, and say "oh, no more references" and
.Free it
-- 8< --
I just ran into some the other day where I *had* to make a TComponent
implement an interface. I got access violations when I was holding onto
one of its children in my main form when I tried loading another file or
exiting the program. And that's me "already knowing better than to do
that" :)
> and I would like to know more code patterns that I should follow when I use
> interfaces. How about some rules regarding the inter-referencing of
> interfaces/objects?
Here are a few 'rules' I go by when using interfaces:
* Keep the interfaces as technology-independent as possible (so, for
example, if you've got an IEventLogger interface, *don't* put a FileName
property in it - your TEmailLogger and TOutputDebugStringLogger won't use
it, and likewise, your TOutputFileLogger won't be using an e-mail address
:)
(ideally, you should be able to completely fake the object behind your
interface, and the other objects wouldn't know - that's one of the
principles behind unit testing, which interfaces are *very* good for)
* Put the technology-dependent items into the constructor if possible (so
for your log-to-file object, create it with the FileName, a la:
TOutputFileLogger.Create(AFileName: String) );
* Make your interfaces cohesive. If you have an object that needs to do
two different jobs, it's best to make two different interfaces and have
the object implement both than to make one giant interface
As for inter-referencing, that's always a tough question. It's always
just a matter of reference-counting.
Essentially, anywhere you may have circular references, you have two
choices:
* Make all back-references 'weak' (i.e. non-reference-counted) -
typically done by storing them as Pointer and typecasting on the spot as
necessary
* Don't just let go of the references or let them go out of scope - tell
them to go away
There are a lot of variations on that last theme. A simple means might be
something like this:
Make an interface:
IBye = interface
[guid]
procedure Goodbye;
end;
Make a Bye() procedure like this:
procedure Bye(var AInterface);
var
Intf : IInterface;
ByeIntf : IBye;
begin
Intf := IInterface(AInterface);
if Supports(Intf,IBye,ByeIntf) then
ByeIntf.Goodbye;
Intf := nil;
end;
Add IBye to any object that refers back to its parent. Implement it by
letting go of any parent, child or other references, e.g.:
type
TMyObject = class(TMyBaseObject,IBye)
private
...
public
procedure Goodbye;
end;
procedure TMyObject.Goodbye;
var
Index : Integer;
CurrentChild : IMyChild;
begin
FParent := nil;
FScreenCaptureObject := nil;
FPreferences := nil;
for Index := 0 to ChildCount-1 do
begin
CurrentChild := Children[Index];
Bye(CurrentChild);
end;
end;
> Second question:
> I've noticed that TInterfaceList is the "recommended" way to go whenever I
> need to hold a list of interfaces. After studying the code for this class,
> I've noticed that TInterfaceList uses internally a TThreadList. However, it
> seems to me that iterating thru a TInterfaceList using Items property might
> be subject of heavy locking/unlocking of the underlying list.
> Since using critical sections for synchronization is a very expensive
> operation, how would you interate efficiently thru a TInterfaceList?
Critical sections being a very expensive operation is,
by and large, a complete myth; it doesn't even rank up there with adding
two strings together :) They're not interrupts, and they don't do
anything funny to the processor - you'd be surprised how mundane they are
beneath the radar.
The actual expensive part of the critical section is the
WaitForSingleObject (bringing that thread to a standstill, but it doesn't
chew up any CPU time once the wait is started) that gets called by the
operating system if you request a critical section that's already in use.
You only get the WaitForSingleObject happening if you try to Acquire the
critical section from another thread while you've got it Acquired in one
thread. You'll never run into it in single-threaded programming, and it's
pretty easy to avoid in multi-threaded programming.
You honestly won't notice. A profiler will prove it as well ;)
That said, remember to use it as an IInterfaceList:
var
List : IInterfaceList;
begin
List := TInterfaceList.Create;
...and you can create a non-critical-section-based list yourself later,
if you see fit. (Copy the implementation, make the FList a TList and
remove all the locking calls :)
> Third question:
> What is the inner mechanism when passing an interface as a "const" parameter
> for a method? What is the difference between passing an interface with
> "const" and without "const"?
(1)
procedure A(const X: IMyInterface);
begin
...
end;
(2)
procedure A(X: IMyInterface);
begin
...
end;
The non-const (2) is exactly like the const (1) with a couple of
additions:
There's an X._AddRef during the call to A in (2).
There's an X._Release during the 'end;' in (2).
By and large, you can use const to not bother with those two calls.
And, of course, as with other 'const' versus 'non-const' parameters, you
can't reassign the local X := ; in example (1), but you can in
example (2).
There are a couple of presentations I put together that you can add to
the pile you may have discovered on your travels, at:
http://nimble.nimblebrain.net/delphi.html, which includes weak pointer
examples, and my cyclic-reference-capable TFundamental class.
My apologies in advance for my tendency towards information overload.
> Thank you,
I hope it helps, Robert!
> Robert
-- Ritchie Annand
Senior Software Architect
Malibu Engineering & Software Ltd.
http://www.malibugroup.comhttp://www.nimblebrain.net