Java Forum / General / July 2006
How does one combine the Adapter and Factory design patterns in a memory efficient way?
Oliver Wong - 28 Jun 2006 20:02 GMT I'm using the adapter pattern, and I've got a factory to generate adapters for passed in adaptees. Let's call the class of the objects getting adapted "Foo", and the Adapter class itself "Bar".
So here are some details specific to my situation:
1) the constructor for Bar is private and visible to the factory (Bar is acting as its own factory), so I can completely control when and how Bar gets instantiated. 2) If an existing adapter for a given instance of Foo doesn't exist yet, I'll want to create a new Bar which matches with it. 3) If there already is a adapter h for Foo, I have to return that existing match, and NOT generate a new one. 4) upon a request for an adapter for null, I return null.
This is relatively easy to do if I have infinite memory. I just create a Map<Foo,Bar>, like so:
<code> public class Bar { private final Foo f;
private Bar(Foo f) { this.f = f; }
private final static Map<Foo,Bar> mapping = new ConcurrentHashMap<Foo,Bar>();
public static Bar make(Foo f) { if (impl == null) { return null; } synchronized (mapping) { if (!mapping.containsKey(f)) { mapping.put(f, new Bar(f)); } Bar b = mapping.get(f); assert returnValue != null; return returnValue; } }
public void newInterfaceWhichModifiesState() { this.f.oldInterfaceWhichModifiesState(); }
public int newInterfaceWhichGetsState() { return this.f.oldInterfaceWhichGetsState(); } } </code>
However, it's possible that the calling code is generating billions and billions of instances of Foo, and then throwing them away after their first use. My Map would prevent the garbage collector from being able to reclaim those instances. I can safely delete those "throwaway Foos" and their matches from my Map, because if there doesn't exist a reference to some instance of Foo anywhere else in the JVM, then it can't possibly ever occur that that instance will ever get passed into my make(Foo) method, in which case, I would never need to return its corresponding Bar.
The two potential-solutions I looked at, Maps of WeakReferences and WeakHashMap, turned out not to satisfy my requirements.
If instead of a Map<Foo,Bar>, I had a Map<Foo,WeakReference<Bar>>, then it's possible that the matching Bar would get garbage collected, but the instance of Foo would get passed in again, and there's no way for me to recover its matching pair, thus violating condition (3) mentioned above.
If I replace Map<Foo,Bar> with WeakHashMap<Foo,Bar>, none of the keys will get GCed, because instances of Bar contain a strong reference to Foo, via the private field f. If I change that field to a weak reference, then it's possible the instance of Foo that's being adapted will get GCed while the corresponding adapter is still in use, resulting in the newInterface() method failing.
I think what I need is some sort of special PairOfWeakReference class such that if there are any references to either an instance of Foo or its corresponding Bar, then BOTH remain uncollectable. However, once there do not exist any strong references to either instances, then the pair become collectable simultaneously.
Using a pair class, as in:
<code> public class Pair<A,B> { public final A a; public final B b;
public Pair(A a, B b) { this.a = a; this.b = b; } } </code>
WeakReference<Pair<Foo,Bar>> won't work either, because the WeakReference class is checking against references to instances of the Pair class, rather than references to the instances of Foo and Bar.
This adapter-factory combination doesn't sound like something that unusual or obscure, so I figure I must be missing something simple from effectively implementing it. Can anyone tell me what that is?
- Oliver
Chris Uppal - 29 Jun 2006 13:45 GMT > 2) If an existing adapter for a given instance of Foo doesn't exist yet, > I'll want to create a new Bar which matches with it. > 3) If there already is a adapter h for Foo, I have to return that existing > match, and NOT generate a new one. OK. Sometimes I've had similar design problems.
But...
> However, it's possible that the calling code is generating billions and > billions of instances of Foo, and then throwing them away after their > first use. If that scenario is even /faintly/ illustrative of realistic usage scenarios then I suggest reconsidering whether you really need to re-use existing adaptors. Just generating a new adaptor on-the-fly (even on every use) would be more efficient in time and space (that's a guess, I admit).
If you decide you do need that facility, then I think that a WeakHashMap<Adaptee, WeakRef<Adaptor>> will do the trick, albeit with some messing around (the JavaDoc for WeakHashMap suggests that approach).
-- chris
Oliver Wong - 29 Jun 2006 15:22 GMT >> 2) If an existing adapter for a given instance of Foo doesn't exist yet, >> I'll want to create a new Bar which matches with it. [quoted text clipped - 16 lines] > would > be more efficient in time and space (that's a guess, I admit). I'm actually adapting a large number of classes (In the low hundreds), and they're each used in different ways. I was hoping for a one-size fits all solution, rather than tailoring the factory for each class.
> If you decide you do need that facility, then I think that a > WeakHashMap<Adaptee, WeakRef<Adaptor>> > will do the trick, albeit with some messing around (the JavaDoc for > WeakHashMap > suggests that approach). The problematic situation I see with this solution is when all strong references of the Adaptor are gone, but there still exists some strong references to the adaptee. The garbage collector then collectors the Adaptor, and then the corresponding adaptee gets passed into the factory, and I can't fufill requirement (3).
- Oliver
Chris Uppal - 29 Jun 2006 17:04 GMT > > If you decide you do need that facility, then I think that a > > WeakHashMap<Adaptee, WeakRef<Adaptor>> [quoted text clipped - 5 lines] > references of the Adaptor are gone, but there still exists some strong > references to the adaptee. If the Adaptors are stateless (or have no interesting state) then you can just generate a new one one demand if the last one has "died due to lack of interest". That way you never have more than one Adaptor associated with each Adaptee, but you may have zero at certain times (whence the "no interestng state" precondition).
If you /do/ need some state persistantly associated with each Adaptee then you could use an additional: WeakHashMap<Adaptee, AdditionalState> to store that state outside the individual Adaptors. If/when you have to regenerate an Adaptor then it would just use the Adaptee->AdditionalState map to point its private final AdditionalState m_additionalState; to that data. The AdditionalState must not refer to either the Adaptor or the Adaptee, but that should be easy to arrange. Similarly the only strong references to the AdditionalState should be in the Map itself and/or in any extant Adaptors.
-- chris
Oliver Wong - 29 Jun 2006 17:53 GMT >> > If you decide you do need that facility, then I think that a >> > WeakHashMap<Adaptee, WeakRef<Adaptor>> [quoted text clipped - 29 lines] > any > extant Adaptors. My main concern is that some of the things I'm adapting are listeners, so you might have something like:
{ OriginalListener ol = getOriginalListener(); AdaptedListener al = getAdaptedListener(ol); this.addListener(al); }
and later on
{ OriginalListener ol = getOriginalListener(); AdaptedListener al = getAdaptedListener(ol); this.removeListener(al); }
and if the listener list uses object identity, rather than object equality, to differentiate between two listeners, the remove might fail as it isn't strictly speaking the same object as the one that was added.
- Oliver
Chris Uppal - 30 Jun 2006 11:24 GMT > My main concern is that some of the things I'm adapting are listeners, > so you might have something like: You keep adding complications to the spec -- next thing you'll be telling us that this has to run on a 1.04 JVM ;-)
But if you are playing games with weak references, object identify, /AND/ the broken implementation of the Observer pattern where the "real" observer doesn't keep strong refs to its actual listener objects, then I doubt if you are going to have too much fun.
I would just give up....
-- chris
Oliver Wong - 03 Jul 2006 15:56 GMT >> My main concern is that some of the things I'm adapting are >> listeners, [quoted text clipped - 13 lines] > > I would just give up.... That's what I've been doing so far, actually. Just putting them in (normal) Maps and forgetting about them. No OutOfMemoryErrors yet... <knocks on wood>
- Oliver
Thomas Hawtin - 30 Jun 2006 18:03 GMT >>> 3) If there already is a adapter h for Foo, I have to return that >>> existing >>> match, and NOT generate a new one.
> The problematic situation I see with this solution is when all strong > references of the Adaptor are gone, but there still exists some strong > references to the adaptee. The garbage collector then collectors the > Adaptor, and then the corresponding adaptee gets passed into the > factory, and I can't fufill requirement (3). If the adaptor is garbage collected (and contains no mutable state), does it really matter if an adaptee has a new adaptor created? No other object can be holding a (strong) reference to it. The only change clients can see is the change of identityHashCode (and References clearing).
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Oliver Wong - 03 Jul 2006 15:56 GMT >>>> 3) If there already is a adapter h for Foo, I have to return that >>>> existing [quoted text clipped - 10 lines] > can be holding a (strong) reference to it. The only change clients can see > is the change of identityHashCode (and References clearing). I think object identity might be important; There's an example elsewhere in this thread where I'd add an (adapted) listener. When I try to remove that same listener, I'd have to use the same adapter in case the List implementation uses object identity for adding or removing elements.
- Oliver
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|