Cyclic object references

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Cyclic object references

Damon
This may be nothing more than a small mental tic that I carry over
from my file-based static typing background, but I thought I'd solicit
people's opinions about it anyway.

I first started programming in C, which had more strict rules about
the order in which files could be included and complied.  For example,
suppose we have the following modules:

a.h a.c

and

b.h b.c

If some data structure defined in header file a.h referenced a data
structure defined in b.h, but then conversely b.h also referenced a.h,
then we have a cyclic dependency between the 2 modules.

This is frowned upon in C, especially since C has used make to build
its projects.  The make utility would not know whether to compile a.h
and a.c first or b.h and b.c first.  When working in C, it is very
much common practice to work out module dependencies so that they are
acyclic, if merely for the sake for compilation ordering.

When I moved on to Java, I was often faced with the same scenario when
modeling bidirectional relationships between 2 objects (especially
when implementing O-R relationships).  For example, suppose we have 2
classes Customer.java and Reservation.java, which reference each
other:

public class Reservation {

    public Customer getCustomer();
}

public class Customer {

    public Reservation getReservation();
}


Java allows for cyclic dependencies by permitting both classes to be
compiled simultaneously:

    javac Customer.java Reservation.java

However, coming from my C heritage, I usually opt for acyclic object
references by using interfaces.  I would have something like an
ICustomer.java interface which Reservation references, and which
Customer implements:


public class Reservation {

    public ICustomer getCustomer();
}

public class Customer implements ICustomer {

    public Reservation getReservation();
}

Now the compilation ordering is acyclic again, since the ordering
would be: compile ICustomer.java first, then Reservation.java, and
finally Customer.java.

Even in Ruby, which is file-based and has a notion of module inclusion
('require' and 'include' commands), I take care to maintain acyclic
references.

So my question now that I'm making a foray in the Smalltalk world is
this:  Should I simply forget about trying to maintain acyclic object
references?  Is it common practice in Smalltalk for objects to have
cyclic dependencies to each other?

How about GNU Smalltalk which is file-based?  Is this a non-issue in
GNU Smalltalk as well?

Many thanks,

Damon


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Stefan Schmiedl
On 30 Apr 2003 17:49:34 -0700,
Damon <[hidden email]> wrote:
>
> So my question now that I'm making a foray in the Smalltalk world is
> this:  Should I simply forget about trying to maintain acyclic object
> references?  Is it common practice in Smalltalk for objects to have
> cyclic dependencies to each other?

If we keep the notion of "compile time" and "run time",
I'd say that Smalltalk objects are acyclic "by definition",
because they can only reference ... objects! Only during
"run time" the type of those objects is known.

Imagine programming in C and referencing all of your data
with (char *) or whatever "generic" pointer type you like.

>
> How about GNU Smalltalk which is file-based?  Is this a non-issue in
> GNU Smalltalk as well?

I hope so.

>
> Many thanks,
>
> Damon


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Damon
Stefan Schmiedl <[hidden email]> wrote in message news:<b8qdbr$cjuok$[hidden email]>...

> On 30 Apr 2003 17:49:34 -0700,
> Damon <[hidden email]> wrote:
> >
> > So my question now that I'm making a foray in the Smalltalk world is
> > this:  Should I simply forget about trying to maintain acyclic object
> > references?  Is it common practice in Smalltalk for objects to have
> > cyclic dependencies to each other?
>
> If we keep the notion of "compile time" and "run time",
> I'd say that Smalltalk objects are acyclic "by definition",
> because they can only reference ... objects! Only during
> "run time" the type of those objects is known.
>
> Imagine programming in C and referencing all of your data
> with (char *) or whatever "generic" pointer type you like.
>


But that still wouldn't solve the cyclic dependency problem in C.  For
example suppose we have:

a.h and a.c containing some structure A

and

b.h and b.c containing some structure B,

where in a.c, we have

                void *some_ptr = malloc(sizeof (B));

but in b.c, we have

                void *some_other_ptr = malloc(sizeof (A));

By naming the structure B in a.c and A in b.c, we would still have a
problem determining which module to compile first.  The fact that the
pointers are 'void *' rather than 'A *' and 'B *' only moves the cylic
dependency problem from the header files to the source files, because
the strucures B and A are named in the a.c and b.c source files.

Similarly, in Smalltalk, we can have:

                a_instanceVar := B new.

in class A, while in class B, we have:

                b_instanceVar := A new.

Offhand I can't think of a case in which this might pose a problem in
Smalltalk, so this may just be a hangover from C programming, but I'd
like to check with more experienced people to see if this a non-issue
or still a concern.

Thanks,

Damon


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Chris Uppal-3
In reply to this post by Damon
Damon wrote:

[re: Cyclic definitions]

You can think of this question two ways.  One is about the practicalities of
cyclic definitions, will the compiler accept them, etc, and how to get around
any problems; the other is about the logical relationships and whether they are
well-formed.

Smalltalk has *less* of a problem with the practicalities, but not none at all.
The absence of declared types means that most code has no "compile-time"
dependency on the classes of the objects that it will manipulate. However,
explicit references to classes have to be compiled into references to the
appropriate global, so there is still some dependency.  That matters if you
have the dependency cross package boundaries; say you have two packages A.pac
and B.pac, which define classes A and B respectively -- if code in class A
refers directly to B, and vice versa then you have a cyclic dependency between
the packages.  That's not a viable situation since Dolphin attempts to resolve
package dependencies before loading, and in this case it would be unable to
load *either* of the two packages since they both require the other to be
loaded first.  (IMO this is a weakness in the Dolphin package system, but it
has never caused me any problems even so)  The simple solution is to put both
classes in the same package.  That's probably the best solution too, since if
the two classes are so intimately interdependent that you can't use one without
the other, then where's the point in packaging them separately ?  A more
extreme solution (which you'll see sometimes used in the image to avoid
unwanted package interdependencies) would be to refer to the "other" class via
a runtime expression like:

    Smalltalk at: #AnotherClass ifAbsent: [...something...]

The other question is about the design that gave rise the cyclic dependencies.
I suspect that *most* (not all) such cycles are symptoms of poor factoring.  If
the two "modules" really are intended to be independent, but nevertheless refer
to each other, then something odd is happening.  Probably something wrong.  So
I think that the C habit of avoiding interdependencies among modules will
probably continue to serve you well in the Smalltalk world *providing* that you
don't overdo it and confuse a "module" (by which I mean a chunk of co-designed
and co-packaged code) with a class.  For instance if you really need a pattern
like your example:

> public class Reservation {
>
>     public Customer getCustomer();
> }
>
> public class Customer {
>
>     public Reservation getReservation();
> }

then Customer and Reservation had better be in the same (conceptual) module.
(They had also better be in the same physical package too -- but that's the
practicality question).

I doubt if this *particular* example really has that co-designed quality about
it, though.  Customers will know how to make reservations, but Reservations
don't (I'd imagine) want to make Customers.  Since the only routine reason for
code to refer to another class is that it uses it as a factory object (sends it
the #new message or similar) I don't expect that Reservation would refer to
Customer at all, in which case (in Smalltalk) there is no physical dependency
of Reservation on Customer.  It may or may not be the case that there's a
logical dependency, that depends on the rest of the design, but I'd be inclined
to suspect not.  If not, then Smalltalk doesn't create the artificial physical
dependency that Java's declarative type system produces, and there would be no
problem with cyclic dependencies.

For instance, Reservation might be a very complicated object, that knows how to
talk 3270 to some reservation system running on a mainframe, whereas Customer
might know about your local database.  In such cases there might be a valid
pressure to put them in separate packages and think of them as logically
independent.  In such cases, in Java, you'd have to make *both* of them into
interfaces, and use the factory object pattern (plus some runtime
configuration) to ensure that neither of the concrete classes referred to the
other, only to the interfaces.  In Smalltalk most of that comes for free, you'd
have to ensure that Customer knew how to find the Reservation factory at
runtime (just as in Java) but the rest of the code would remain unchanged.  In
effect Smalltalk has the only-use-interfaces and Factory Object patterns built
into the semantics of the language.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Stefan Schmiedl
In reply to this post by Damon
On 1 May 2003 01:36:54 -0700,
Damon <[hidden email]> wrote:

> Stefan Schmiedl <[hidden email]> wrote in message news:<b8qdbr$cjuok$[hidden email]>...
>>
>> Imagine programming in C and referencing all of your data
>> with (char *) or whatever "generic" pointer type you like.
>>
>
>
> But that still wouldn't solve the cyclic dependency problem in C.  For
> example suppose we have:
>
> a.h and a.c containing some structure A
>
> and
>
> b.h and b.c containing some structure B,
>
> where in a.c, we have
>
>                 void *some_ptr = malloc(sizeof (B));
>
> but in b.c, we have
>
>                 void *some_other_ptr = malloc(sizeof (A));

ahhh... If you are a Real Programmer (like Mel:-) and do everything
with (char *) then you will also be able to hardcode the struct sizes

:-)

s.


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Bill Schwab-2
> ahhh... If you are a Real Programmer (like Mel:-) and do everything
> with (char *) then you will also be able to hardcode the struct sizes

(char*)???  STRUCTS???  What's with all of this high level stuff!!  Real
programmers write native instructions w/ none of those reference books that
wimps use :)

Actually, I didn't see it myself, but I heard from a reliable first hand
witness about a consultant who patched the _object code_ for a main frame's
serial communications driver.  Got it right on the first try.

Have a good one,

Bill

--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Blair McGlashan
In reply to this post by Chris Uppal-3
"Chris Uppal" <[hidden email]> wrote in message
news:3eb11903$0$45175$[hidden email]...
> Damon wrote:
>
> [re: Cyclic definitions]
>
>.... However,
> explicit references to classes have to be compiled into references to the
> appropriate global, so there is still some dependency.  That matters if
you
> have the dependency cross package boundaries; say you have two packages
A.pac
> and B.pac, which define classes A and B respectively -- if code in class A
> refers directly to B, and vice versa then you have a cyclic dependency
between
> the packages.  That's not a viable situation since Dolphin attempts to
resolve
> package dependencies before loading, and in this case it would be unable
to
> load *either* of the two packages since they both require the other to be
> loaded first.  (IMO this is a weakness in the Dolphin package system, but
it
> has never caused me any problems even so)

It is a deliberate weakness (that would be relative easily overcome),
because...

>...The simple solution is to put both
> classes in the same package.  That's probably the best solution too, since
if
> the two classes are so intimately interdependent that you can't use one
without
> the other, then where's the point in packaging them separately ?

...of precisely that.

It is not difficult to resolve cyclic references by one of a number of
techniques, including (as you say) having just one package, or introducing a
common base package. The advantage of this "discipline", is that one ends up
with a clean hierarchy of packages that actually allows the re-use of base
packages without bringing along loads of unwanted pieces. It is also key to
successful image stripping, since the removal of entire packages is much
more effective than eliminating unreferenced globals and methods at a finer
granularity.

>...A more
> extreme solution (which you'll see sometimes used in the image to avoid
> unwanted package interdependencies) would be to refer to the "other" class
via
> a runtime expression like:
>
>     Smalltalk at: #AnotherClass ifAbsent: [...something...]

Creating a symbolic reference is indeed one easy way to break a dependency,
but as Chris implies this should be used with care since this reference is
now not seen as implying a dependency in the referent, and so may result in
issues at deployment time.

>
> The other question is about the design that gave rise the cyclic
dependencies.
> I suspect that *most* (not all) such cycles are symptoms of poor
factoring.

No disagreement with that here.

>...If
> the two "modules" really are intended to be independent, but nevertheless
refer
> to each other, then something odd is happening.  Probably something wrong.
So
> I think that the C habit of avoiding interdependencies among modules will
> probably continue to serve you well in the Smalltalk world *providing*
that you
> don't overdo it and confuse a "module" (by which I mean a chunk of
co-designed
> and co-packaged code) with a class.

Precisely. At the risk of repeating myself, that is why we don't support
cyclic dependencies between packages. Its also why we eschewed the
traditional 'Undeclared' mechanism of Smalltalk (by which forward references
to as yet undefined globals get entered into a special dictionary), because
we don't think it is needed or helpful in the long run. YMMV.

>....

Regards

Blair


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Bill Schwab-2
Blair,

Ditto to pretty much everything you said.  My one wish would be to have
better visualization of the dependencies between packages (both "up and
down").  Easier said than done, I know (which is why I haven't already done
it).  My slightly battered 5.1 image contains 222 packages.

Speaking of stripping, congratualations are in order.  Lagoon of 5.1 (quite
correctly) removed ZLib from my deployed apps.  It was easy to find, and
easier to fix by adding a manual prerequisite that probably should have been
required all along.

Have a good one,

Bill

--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Damon
In reply to this post by Chris Uppal-3
"Chris Uppal" <[hidden email]> wrote in message


>Customers will know how to make reservations, but Reservations
> don't (I'd imagine) want to make Customers.  Since the only routine reason for
> code to refer to another class is that it uses it as a factory object (sends it
> the #new message or similar) I don't expect that Reservation would refer to
> Customer at all, in which case (in Smalltalk) there is no physical dependency
> of Reservation on Customer.  It may or may not be the case that there's a
> logical dependency, that depends on the rest of the design, but I'd be inclined
> to suspect not.  If not, then Smalltalk doesn't create the artificial physical
> dependency that Java's declarative type system produces, and there would be no
> problem with cyclic dependencies.

Very well put Chris. Thank you.


> In such cases, in Java, you'd have to make *both* of them into
> interfaces, and use the factory object pattern (plus some runtime
> configuration) to ensure that neither of the concrete classes referred to the
> other, only to the interfaces.  In Smalltalk most of that comes for free,
> you'd have to ensure that Customer knew how to find the Reservation factory at
> runtime (just as in Java) but the rest of the code would remain unchanged.  In
> effect Smalltalk has the only-use-interfaces and Factory Object patterns built
> into the semantics of the language.

This really nails it down for me.  I've been so mired in Algol-like
languages in which even type declarations introduce dependencies, that
I hadn't really noticed that there were 2 design issues at work:  type
reference dependencies and factory object dependencies.  Your comments
about how Smalltalk need only be concerned with the latter helps
clarify the differences between the two.

Having recently started learning Smalltalk, I'm beginning to regret
all the years I've wasted on those other languages, where issues such
as those described above (physical dependencies, type reference
dependencies, and factory dependencies) all seem to creep up and blur
together.  The amount of energy I've spent sorting out classes and
modules to keep them decoupled is mindnumbing.  I could have been so
much more productive much earlier.

If I could only get all those years back.

Damon


Reply | Threaded
Open this post in threaded view
|

Re: Cyclic object references

Stefan Schmiedl
In reply to this post by Bill Schwab-2
On Thu, 1 May 2003 07:20:01 -0500,
Bill Schwab <[hidden email]> wrote:

>> ahhh... If you are a Real Programmer (like Mel:-) and do everything
>> with (char *) then you will also be able to hardcode the struct sizes
>
> (char*)???  STRUCTS???  What's with all of this high level stuff!!  Real
> programmers write native instructions w/ none of those reference books that
> wimps use :)
>
> Actually, I didn't see it myself, but I heard from a reliable first hand
> witness about a consultant who patched the _object code_ for a main frame's
> serial communications driver.  Got it right on the first try.
>
> Have a good one,
>

http://ars.userfriendly.org/cartoons/?id=19990508

:-D

having a good one,
s.