How does one combine the Adapter and Factory design patterns in a memory efficient way?

O

Oliver Wong

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
 
C

Chris Uppal

Oliver said:
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
 
O

Oliver Wong

Chris Uppal said:
OK. Sometimes I've had similar design problems.

But...


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).

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
 
C

Chris Uppal

Oliver said:
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.

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
 
O

Oliver Wong

Chris Uppal said:
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.

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
 
C

Chris Uppal

Oliver said:
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
 
T

Thomas Hawtin

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
 
O

Oliver Wong

Chris Uppal said:
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....

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
 
O

Oliver Wong

Thomas Hawtin said:
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).

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top