Board index » delphi » Using interfaces: Object that can be both parent and child

Using interfaces: Object that can be both parent and child

Normally you would derive an Object that can have kids from
TInterfacedObject and you would derive objects that are contained by a
parent from TAggregatedObject or TContainedObject.

A problem arises when you have an object that should be able to act as
both a "root" parent and a child depending on the context. Example:
Client that should be able to stand on its own and be contained in a
list of past contacts for an employee.

I remember having seen an article or newsgroup post with an example
implementation of an (interfaced) object able to do this.
Unfortunately, I can't find it anymore.

If this rings any bells, links would be appreciated.

Thanks and a happy, fun 2003!
Marjan
_________________________
Marjan Venema - BJM Software
info(AT)bjmsoftware(DOT)com
http://www.bjmsoftware.com

 

Re:Using interfaces: Object that can be both parent and child


You seem to be confusing the purpose of these classes.

An aggregated object implements an interface on behalf of an object.
Externally it looks like the object implements all of the interfaces.  When
QueryInterface is called the outer object is queried instead of the
aggregated object.

A contained object is the same as above, except that when QueryInterface is
called the sub-object is queried instead of the outer object.

What you are talking about is relationships between objects, rather than how
they implement interfaces.

Customer (has many) order (has many) orderline.

In this case the interface would just return another interface for another
object.  All objects would descend from TInterfacedObject.  The problem is
when the Order needs a reference back to the Customer, then you have two
objects referencing each other and never being released.  Some people call
_Release to overcome this, but this can often result in an Order referencing
a Customer which has been destroyed.

--
Pete
====
http://www.DroopyEyes.com
Audio compression, DIB controls, FastStrings, Popup Killer

http://www.HowToDoThings.com
Read / write articles.

Re:Using interfaces: Object that can be both parent and child


Quote
> Customer (has many) order (has many) orderline.

> In this case the interface would just return another interface for another
> object.  All objects would descend from TInterfacedObject.  The problem is
> when the Order needs a reference back to the Customer, then you have two
> objects referencing each other and never being released.  Some people call
> _Release to overcome this, but this can often result in an Order referencing
> a Customer which has been destroyed.

That is just the point. Aside from how they deal with interfaces,
TAggregatedObject (notice the -d-) and its descendant TContainedObject also
deal with refcounting differently than TInterfacedObject (the one you would
use as an AggregateObject - notice the lack of the -d-).

The example I remember (but can't find) used an object capable of acting both
like a normal TInterfacedObject and like a TContainedObject to prevent this
deadlock.

But maybe I am dreaming...
Marjan
_________________________
Marjan Venema - BJM Software
info(AT)bjmsoftware(DOT)com
http://www.bjmsoftware.com

Re:Using interfaces: Object that can be both parent and child


"Marjan Venema" <i...@bjmsoftware.OhNoNotThis.com> a crit dans le message
news: VA.0000012c.0130f__BEGIN_MASK_n#9g02mG7!__...__END_MASK_i?a63jfAD$z__@bjmsoftware.ohnonotthis.com...

Quote
> The example I remember (but can't find) used an object capable of acting
both
> like a normal TInterfacedObject and like a TContainedObject to prevent
this
> deadlock.

Marjan, what you need is an implementing class that looks like this :

TImplClass = class(TInterfacedObject, IThing)
private
  fParent: Pointer;
public
  function GetParent: IThing;
  constructor Create(const Parent: IThing);
end;

constructor TImplClass.Create(const Parent: IThing);
begin
  inherited Create;
  fParent := Pointer(Parent); // weak reference, not refcounted
end;

function GetParent: IThing;
begin
  Result := IThing(fParent); // convert to IThing increments refcount
end;

Feel free to call if you need to discuss this further.

Joanna

--
Joanna Carter
Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker

Re:Using interfaces: Object that can be both parent and child


That is a {*word*193} hack in my opinion.  That is exactly the sort of situation I
described where the child-object still exists and references the parent
object which has been destroyed.

--
Pete
====
http://www.DroopyEyes.com
Audio compression, DIB controls, FastStrings, Popup Killer

http://www.HowToDoThings.com
Read / write articles.

Re:Using interfaces: Object that can be both parent and child


Maybe if you describe the layout a little someone could offer a suggestion.

--
Pete
====
http://www.DroopyEyes.com
Audio compression, DIB controls, FastStrings, Popup Killer

http://www.HowToDoThings.com
Read / write articles.

Re:Using interfaces: Object that can be both parent and child


I am not sure I can make this very clear, as it isn't fully cristalized
in my mind yet either, but I'll try.

What I would like is an TInterfacedParentChildObject. You could think
of it as the base class in a Composite pattern. However, the class
should not determine whether it is a leaf or a node. If it gets created
with a parent it should act as a child (with possible children of its
own). If it gets created without a parent it should start life acting
as an orphan (root node). However, when it gets assigned a parent it
should change its behaviour to act as a parented node.

Example
Customer, Orders and OrderLines

I can view Orders as orders where the Customer is known as an
attribute. In RefCounting terms the Order would have to act as an
InterfacedObject. OrderLines would act as ContainedObjects.

But I can also view Customers and their Orders. In this case the
Customer is instantiated and in RefCounting terms the Order now needs
to act as a ContainedObject.

I think the question may not be whether the Order has a "parent" or not
- the Customer is always known -, but where it lives (or should I say
"is shown") in a presentation structure:

In a list such as
- Order1 (Customer1)
  - Line1
- Order2 (Customer9)
  - Line2
  - Line3
- Order3 (Customer10)
the Orders would all have to act as InterfacedObjects and the Customers
as ContainedObjects. In this case the "presentation life time" of the
Customer instance is determined by the Order. When I remove an Order
from the list, the Customer instance can also be freed (if there are no
other Orders referencing it or there is a Customer instance for each
Order).

In a structures such as
- Customer1
  - Order1
  - Order5
- Customer2
- Customer3
  - Order9
the Orders would all have to act as ContainedObjects and the Customers
as InterfacedObjects.

I hope this clarifies what I am looking for.
TIA for any suggestions, Marjan
_________________________
Marjan Venema - BJM Software
info(AT)bjmsoftware(DOT)com
http://www.bjmsoftware.com

Re:Using interfaces: Object that can be both parent and child


Quote
"Joanna Carter" <joan...@btinternet.com> wrote in message

news:3e130a2a$1@newsgroups.borland.com...

Quote
> "Marjan Venema" <i...@bjmsoftware.OhNoNotThis.com> a crit dans le message
> news: VA.0000012c.0130f__BEGIN_MASK_n#9g02mG7!__...__END_MASK_i?a63jfAD$z__@bjmsoftware.ohnonotthis.com...

> > The example I remember (but can't find) used an object capable of acting
> both
> > like a normal TInterfacedObject and like a TContainedObject to prevent
> this
> > deadlock.

> Marjan, what you need is an implementing class that looks like this :

> TImplClass = class(TInterfacedObject, IThing)
> private
>   fParent: Pointer;
> public

Doh...I think.

While staring at this I think I found _the_ solution;

TImplClass = class(TInterfacedObject, IThing)
private
  fParent: IThing;
protected
  procedure _Release; stdcall;
public
  function GetParent: IThing;
  constructor Create(const Parent: IThing);
end;

constructor TImplClass.Create(const Parent: IThing);
begin
  inherited Create;
  fParent := Parent;
end;

function GetParent: IThing;
begin
  Result := fParent;
end;

procedure TImplClass._Release;
begin
  FParent := nil;
  inherited _Release;
end;

_Release seems to be the solution, since it is called automatically by the
system when a variable goes out of scope or is nil'd.

Should solve A -> B -> A memory leaks without casting...I think.  Any
dissenters?

John

Re:Using interfaces: Object that can be both parent and child


Quote
> Should solve A -> B -> A memory leaks without casting...I think.  Any
> dissenters?

I had just come to a similar conclusion. I was being baffled by the
QueryInterface troubles of using TAggregatedObject or TContainedObject, when
all I needed was a simple way to control the RefCount depending on whether an
object was a root-node or sub-node. Hadn't thought of re-implementing _Release
though...

Thanks, Marjan
_________________________
Marjan Venema - BJM Software
info(AT)bjmsoftware(DOT)com
http://www.bjmsoftware.com

Re:Using interfaces: Object that can be both parent and child


Quote
"Marjan Venema" <i...@bjmsoftware.OhNoNotThis.com> wrote in message

news:VA.00000135.0224790b@bjmsoftware.ohnonotthis.com...

Quote
> > Should solve A -> B -> A memory leaks without casting...I think.  Any
> > dissenters?

> I had just come to a similar conclusion. I was being baffled by the
> QueryInterface troubles of using TAggregatedObject or TContainedObject,
when
> all I needed was a simple way to control the RefCount depending on whether
an
> object was a root-node or sub-node. Hadn't thought of re-implementing
_Release
> though...

Yea...it's a problem that much ado has been written about.  I've thought
about it many times...basically, we need a Free or Cleanup call.

Then it struck me..._Release is called automatically.  "Should" be the
perfect place to add any niling or cleanup code for an object instance.

John

Re:Using interfaces: Object that can be both parent and child


Quote
"John Elrick" <jelr...@adelphia.net> wrote in message

news:3e148eb2@newsgroups.borland.com...

Quote

> procedure TImplClass._Release;
> begin
>   FParent := nil;
>   inherited _Release;
> end;

I was using _AddRef and _Release to handle certain things for the same
reason. Works really well. But my understanding is, if you want to write
.Net managed code, this will not be possible - there'll be no access to
these functions (I may be wrong and welcome any corrections).

--
Wayne Niddery - Logic Fundamentals, Inc. (www.logicfundamentals.com)
"Democracy, without that guarantee of liberty, is merely a method of
selecting tyrants." - Alan Nitikman

Re:Using interfaces: Object that can be both parent and child


On Wed, 01 Jan 2003 13:09:59 +0100, Marjan Venema

Quote
<i...@bjmsoftware.OhNoNotThis.com> wrote:
> Normally you would derive an Object that can have kids from
> TInterfacedObject and you would derive objects that are contained by a
> parent from TAggregatedObject or TContainedObject.
> A problem arises when you have an object that should be able to act as
> both a "root" parent and a child depending on the context. Example:
> Client that should be able to stand on its own and be contained in a
> list of past contacts for an employee.
> I remember having seen an article or newsgroup post with an example
> implementation of an (interfaced) object able to do this.
> Unfortunately, I can't find it anymore.
> If this rings any bells, links would be appreciated.

Hi there, Marjan.  I've got a slightly complex but wrapped solution that
does something along the lines of what you're looking for, because I ran
into the same sorts of problems you did.

Take a look at my sample code in:
http://members.shaw.ca/nimble/download/InterfacesExplorer.zip

I've got a unit in there called NodeFundamentals.  It's a bit more
involved than your basic TInterfacedObject, though, so give it a look-see
first to see if it's even appropriate to your needs.

In my scheme, you inherit from my TFundamental, TChildFundamental,
TParentFundamental or TNodeFundamental.  TNodeFundamental is the kind
that can behave like a parent, a child, or both.

The children all point to their parents with weak interface pointers (you
don't have to take care of that detail - just access the Parent property
when you need to), so you can get rid of the parent and all of the
children will go away.

I have some extra features in the TFundamental for additional reference-
counting abilities, which you can use to get around other reference-
counting issues (I've suffered from a lot of circular references in my
day, especially in objects used for communications):

Using IFundamental.Attach (or AttachI) makes a two-way reference to
another object.  You would only use this feature when (A) you have
circular references, or (B) when you want to be able to 'force' an object
to go away, even if something is referring to it.

When you want to 'disconnect' from that other object, you use Detach.  If
you want your object to go away, you call  Drop - this will request a
Detach from all attached objects, which should drop your reference count
to just local variables (which will drop to zero when your call is
finished or when you set it to nil), and will also call Drop on all child  
objects (to tell anything holding onto them to let go).  There's a
DropAndNilI(x) (like SysUtil's DropAndNil) to both call drop and set the
interface pointer to nil.

Override the Detaching() method to make an object respond to a
detachment.  For example, if you have a User: IMyUser property, and
you've AttachI()'d to it, and something else Drop()s the user, you
override Detaching to see if it's your User property that's detaching,
and set it to nil if it is (thus dropping the user's reference count so
that it can go away)

Let me know if it meets your needs :)

-- Ritchie Annand
Senior Software Architect
Malibu Engineering & Software Ltd.
Bus: http://www.malibugroup.com
Per: http://members.shaw.ca/nimble

Re:Using interfaces: Object that can be both parent and child


"John Elrick" <jelr...@adelphia.net> a crit dans le message news:
3e148...@newsgroups.borland.com...

Quote
> While staring at this I think I found _the_ solution;

> TImplClass = class(TInterfacedObject, IThing)
> private
>   fParent: IThing;
> protected
>   procedure _Release; stdcall;
> public
>   function GetParent: IThing;
>   constructor Create(const Parent: IThing);
> end;

> constructor TImplClass.Create(const Parent: IThing);
> begin
>   inherited Create;
>   fParent := Parent;
> end;

> function GetParent: IThing;
> begin
>   Result := fParent;
> end;

> procedure TImplClass._Release;
> begin
>   FParent := nil;
>   inherited _Release;
> end;

> _Release seems to be the solution, since it is called automatically by the
> system when a variable goes out of scope or is nil'd.

> Should solve A -> B -> A memory leaks without casting...I think.  Any
> dissenters?

Aye! I mean Nay! I mean...

This only works if you have nodes that have no concept of their children,
but if you add the following to the interface...

IThing = interface
[GUID]
  function GetParent: IThing;
  procedure AddChild(const Item: IThing);
end;

...then alter the implementing class...

TImplClass = class(TInterfacedObject, IThing)
private
  fParent: IThing;
  fChildren: IInterfaceList;
protected
  function _Release: Integer; stdcall;
public
  procedure AddChild(const Item: IThing);
  function GetParent: IThing;
  constructor Create(const Parent: IThing);
end;

constructor TImplClass.Create(const Parent: IThing);
begin
  inherited Create;
  fParent := Parent;
end;

procedure TImplClass.AddChild(const Item: IThing);
begin
  if fChildren = nil then
    fChildren := TInterfaceList.Create;
  fChildren.Add(Item);
end;

function TImplClass.GetParent: IThing;
begin
  Result := fParent;
end;

function TImplClass._Release: Integer;
begin
  FParent := nil;
  Result := inherited _Release;
end;

...now try the following test...

var
  n: IThing;
begin
  n := TThing.Create(nil);
  n.AddChild(TThing.Create(n));
...
end;

... you will find that neither of the 'things' are destroyed.

But your principle is correct if you change the _Release method to this...

function TImplClass._Release: Integer;
begin
  FChildren := nil;
  Result := inherited _Release;
end;

Why is it the simplest solutions are sometimes the hardest to find??

;~}

Joanna

--
Joanna Carter
Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker

Re:Using interfaces: Object that can be both parent and child


function TOneClass._Release: Integer;
begin
  Result := inherited _Release;
  if RefCount = 1 then
    FOther := nil;
end;

This method also works for mutual reference interfaces.

Joanna

--
Joanna Carter
Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker

Re:Using interfaces: Object that can be both parent and child


nice monolog Joanna <g>
Go to page: [1] [2]

Other Threads