Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.java.programmer > #5788
| 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> |
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 | Next — Previous in thread | Find similar
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