Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.java.programmer > #5788

Re: Automatic linking of related objects in constructor

From supercalifragilisticexpialadiamaticonormalizeringelimatisticantations <supercalifragilisticexpialadiamaticonormalizeringelimatisticantations@averylongandannoyingdomainname.com>
Newsgroups comp.lang.java.programmer
Subject Re: Automatic linking of related objects in constructor
Date 2011-06-29 19:58 -0400
Organization supercalifragilisticexpialadiamaticonormalizeringelimatisticantations
Message-ID <iuge6h$moo$1@speranza.aioe.org> (permalink)
References <eYKdnUijTfibapfTnZ2dnUVZ_uadnZ2d@westnet.com.au> <construction-20110629121856@ram.dialup.fu-berlin.de>

Show all headers | View raw


On 29/06/2011 6:20 AM, Stefan Ram wrote:
> "Qu0ll"<Qu0llSixFour@gmail.com>  writes:
>> Is it safe to make a call to A's method addB(B b)
>> in that B constructor passing in "this"?
>
>    I am not sure whether I understand what »safe« means in this
>    context. I do not expect incomplete-construction problems,
>    when it is called at the very end of the constructor's body.

That can still be unsafe, if B may have subclasses. If B is final, 
there's no problem. If the application is single-threaded and B subclass 
constructors can't throw exceptions, the reference in the A object won't 
actually be acted on until construction is complete.

>    However, it might not agree with the semantics of the word
>    »constructor«, which designates an entity that initializes an
>    object.

I'd say it might or it might not; if the semantics of a B object include 
that B objects are tracked, then it might.

>    This might to be a case of incomplete isolation (separation,
>    encapsulation) of concerns of these two classes.

Agreed.

But if B objects are supposed to all be tracked by an A, that suggests 
that B objects' lifecycles should be managed by A objects rather than 
directly by end-users, so B should probably have default-access 
constructors and A should have B-generating factory methods. Of course, 
this complicates matters if it's desired that B have subclasses. Either 
A has to know about the whole B-descended class hierarchy (so extending 
that requires modifying A), or adding novel B subclasses requires adding 
novel A subclasses, or ...

In that case, you may need B to take care of these things in a different 
way. B would have a mix of protected abstract and public final methods, 
with the public API being the final methods, some of which call some of 
the protected abstract methods to do their work. The final methods make 
sure B's fields maintain B's invariants; the abstract methods allow some 
customization of B's behavior via subclassing. And the final methods 
check an isInitialized flag, which B's constructor sets to false, which 
is private so cannot be set by subclass constructors, and which a 
registerThisThingy method sets after submitting the B instance to an A. 
So trying to use an incompletely-initialized B will throw exceptions, 
and a completely-initialized one has been registered with an A.

This still carries issues such as a subclass being able to define its 
own fields and methods, or make some of the abstract B methods public 
instead of protected, allowing an unregistered subclass instance to be 
used, though none of the B API methods will be usable.

A better option may be to make B final (and go either the 
constructor-registers-it-as-its-last-act route or the 
an-A-is-a-B-factory route) and use delegates that are subclassable to 
provide customizability if needed; so a B has-a C which some B methods 
punt to (an instance of the strategy pattern) and though B is final C is 
not (indeed is likely an interface). You construct a C, then pass it and 
an A to B's constructor or pass it to an A factory method that returns a 
B. A needn't know about C's subclasses in the latter case, only about C 
itself. If C methods need access to the B object, the C interface can 
specify a method by which the B registers itself as the C's parent and a 
C implementation can store this B reference and call B's public API.

If the C needs access to B internals, though, this is trickier. It's 
still doable, even with moderately strong encapsulation, though: you can 
have an interface D that represents B's C-visible, but otherwise 
private, extra API, and the B class can have a private D-implementing 
inner class with each B instance carrying a D instance inside it that 
connects through to some internal B fields and/or methods. The B 
instance passes this object to the C it's constructed around, and the C 
can then use the D to talk to B's innards. The D methods can still 
enforce various invariants, while allowing the C access to stuff not 
ordinarily visible to B's clients. The C is in effect a "friend" of B in 
a C++ish sense. Of course, a malicious C can partially break B's 
encapsulation by exposing its D object to clients. A variant of this 
with slightly stronger encapsulation makes C an abstract class with a 
concrete, and final, implementation of the D-receiving method and a 
private final D field, plus protected final methods that punt to the D. 
Because they're final a C subclass can't simply make these public, even 
with a pure call-super override, but it still can expose the D 
indirectly via a set of public methods that connect through to the 
D-calling protected methods.

Which just goes to show that encapsulation should generally be about 
making it harder for clients and extenders to make mistakes or end up 
depending on internals in ways that break forward compatibility with 
future versions of your classes, but not about security against 
deliberate injection of malicious classes. That's a much harder problem 
that is best left to the experts in JVM security matters.

Myself, I'd probably go a quasi-functional route:

* A is a B-factory and the factory method takes a C.
* B is a final class that enforces the B invariants/contract and
   maintains internally all state needed for a generic B, but calls
   its C to delegate the details of how to perform various
   computations. It has a default-access constructor that takes a C
   and is in the same package as A.
* C is an interface that specifies methods whose contracts are,
   generally, pure-functional: the method takes some arguments and
   is supposed to compute a result from them, that's all. C's usual
   use is to customize B's behavior or algorithmic strategy -- e.g.
   a C method might be sort() and one C might implement it as
   mergesort and another as quicksort. There is no D as B is designed
   to make sure no C needs access to the enclosing B to do its job
   beyond what B provides via arguments to C methods.

This doesn't allow quite as much customizability as if C had more access 
to the enclosing B, let alone if B were subclassable, but it's safe and 
probably sufficient for most scenarios of this type where you want 
automatically-registered objects that are to some extent customizable.

Note a second concern nobody raised here yet is the *end* of a B life. 
The A class may need some way of getting rid of no-longer-needed Bs to 
avoid packratting them and defeating the garbage collector. An explicit 
unregister method has the problem that it breaks the "all Bs are 
registered with an A" part of B's contract if anyone calls it and then 
hangs onto and uses a B reference. If you go the 
B-has-an-isInitialized-check route, you can have the unregister method 
of A put the B (via a default-access final method) back into the 
any-B-operation-throws-exceptions state, but that feels unsatisfactory. 
Better is to have A keep its Bs in a List<WeakReference<B>> or as keys 
in a WeakHashMap<B,Foo> or something, so the A registry only weakly 
holds the Bs in it and a B that becomes otherwise unreachable disappears 
from the A automatically and becomes eligible for GC. If there's any 
other B-EOL cleanup A should do it may be able to manage this by 
suitable use of ReferenceQueues and, perhaps, a custom WeakReference 
subclass that has an added field to store an abstraction representing 
the cleanup to do with the associated B dies; when the B dies, the 
reference is enqueued, and something periodically polls the queue and 
pops one of the cleanup job objects which it then performs. (One awkward 
bit here is the likely need for explicit threading here, as 
ReferenceQueue doesn't provide any event-driven callbacky interface.)

Back to comp.lang.java.programmer | Previous | NextPrevious in thread | Find similar


Thread

Automatic linking of related objects in constructor "Qu0ll" <Qu0llSixFour@gmail.com> - 2011-06-29 19:56 +1000
  Re: Automatic linking of related objects in constructor "Qu0ll" <Qu0llSixFour@gmail.com> - 2011-06-29 19:58 +1000
  Re: Automatic linking of related objects in constructor Lew <noone@lewscanon.com> - 2011-06-29 07:28 -0400
  Re: Automatic linking of related objects in constructor Eric Sosman <esosman@ieee-dot-org.invalid> - 2011-06-29 08:29 -0400
    Re: Automatic linking of related objects in constructor Tom Anderson <twic@urchin.earth.li> - 2011-06-30 22:51 +0100
      Re: Automatic linking of related objects in constructor supercalifragilisticexpialadiamaticonormalizeringelimatisticantations <supercalifragilisticexpialadiamaticonormalizeringelimatisticantations@averylongandannoyingdomainname.com> - 2011-06-30 18:23 -0400
  Re: Automatic linking of related objects in constructor markspace <-@.> - 2011-06-29 09:17 -0700
  Re: Automatic linking of related objects in constructor supercalifragilisticexpialadiamaticonormalizeringelimatisticantations <supercalifragilisticexpialadiamaticonormalizeringelimatisticantations@averylongandannoyingdomainname.com> - 2011-06-29 19:58 -0400

csiph-web