Summary
The Object Adapter Design Pattern is often used for modifying an interface into another interface that the client expects. A drawback with this pattern is that you need to implement all the methods of the target interface. In this newsletter, solve the problem with dynamice proxies
Advertisement
In the latest upgrade of my website (many more to come soon)
I have added an RSS
feed for The Java(tm) Specialists' Newsletter for those who would prefer receiving
the newsletter via RSS rather than as an email. The RSS feed
will always only have the most current newsletter on it, but
the old issues will, as always, still be available for free
on our website.
Object Adapter based on Dynamic Proxy
A few weeks ago, I presented my Design
Patterns Course to a wonderfully inspiring audience in
Austria. One of the three Doctors of Computer Science in the
course, Dr Klaus Wiederaenders suggested an approach to solve
a problem that had been bugging me with the Object Adapter
Pattern.
There are two different types of Adapter Design Patterns:
Object Adapter and Class Adapter. The Object Adapter has the
advantage that it can be used to adapt a whole hierarchy of
objects, whereas the Class Adapter has the advantage that you
do not need to override all the methods.
Application of Object Adapter
Java 5 has a neat little feature, that is not widely known.
You can change the return type of overridden methods. For
example, clone() now returns the correct type of the object,
so you do not need to downcast the result anymore.
One of my annoyances with Java has been that
Collection.toArray() returns an Object[] and not the correct
type. Say you have a collection containing Strings, then you
have to pass a String[] into the toArray() method. This
seems clumsy to me. It would have been nice if this had been
changed in Java 5. However, generics cannot solve the
problem due to erasure. There is no handle to the type of
generic, once the code has been compiled. You therefore have
to change the construction of the collection object to also
have a handle to the class of the generic type.
My first solution was to write a Class Adapter, which
extended java.util.ArrayList with my adapter, which I called
BetterArrayList. I have put these classes in a package so
that we can do static imports later on:
package com.maxoft.tjsn.util;
import java.lang.reflect.Array;
import java.util.*;
public class BetterArrayListextends ArrayList {
private final Class valueType;
public BetterArrayList(int initialCapacity, Class valueType) {
super(initialCapacity);
this.valueType = valueType;
}
public BetterArrayList(Class valueType) {
this.valueType = valueType;
}
public BetterArrayList(Collection extends T> ts,
Class valueType) {
super(ts);
this.valueType = valueType;
}
// You can modify the return type of an overridden method in
// Java 5, with some restrictions.public T[] toArray() {
return toArray((T[]) Array.newInstance(valueType, size()));
}
}
We can now use this in our code instead of the ArrayList, and
then we do not need to have such an awkward syntax for
converting it to a type-safe array. We have to pass the
class object into the constructor, but the compiler checks
that it is the correct class object for the generic type.
package com.maxoft.tjsn.util;
public class BetterArrayListTest {
public static void main(String[] args) {
BetterArrayList names =
new BetterArrayList(String.class);
names.add("Wolfgang");
names.add("Leander");
names.add("Klaus");
names.add("Reinhard");
String[] nameArray = names.toArray();
for (String s : nameArray) {
System.out.println(s);
}
}
}
This would be a reasonable solution if we only ever wanted to
use ArrayLists. However, we have to write a class adapter
for every collection class that we might want to use.
Here is a new interface, called the BetterCollection, that
extends the Collection interface, and changes the return type
of the toArray() method:
We can then implement that interface in an object adapter
(note how much more code this is!):
package com.maxoft.tjsn.util;
import java.lang.reflect.Array;
import java.util.*;
public class BetterCollectionObjectAdapterimplements BetterCollection {
private final Collection adaptee;
private final Class valueType;
public BetterCollectionObjectAdapter(Collection adaptee,
Class valueType) {
this.adaptee = adaptee;
this.valueType = valueType;
}
public T[] toArray() {
return adaptee.toArray((T[]) Array.newInstance(valueType,
adaptee.size()));
}
// this is a typical problem with the Object Adapter Design
// Pattern - you have implement all the methods :-(public int size() {
return adaptee.size();
}
public boolean isEmpty() {
return adaptee.isEmpty();
}
public boolean contains(Object o) {
return adaptee.contains(o);
}
public Iterator iterator() {
return adaptee.iterator();
}
public T[] toArray(T[] ts) {
return adaptee.toArray(ts);
}
public boolean add(T t) {
return adaptee.add(t);
}
public boolean remove(Object o) {
return adaptee.remove(o);
}
public boolean containsAll(Collection> c) {
return adaptee.containsAll(c);
}
public boolean addAll(Collection extends T> ts) {
return adaptee.addAll(ts);
}
public boolean removeAll(Collection> c) {
return adaptee.removeAll(c);
}
public boolean retainAll(Collection> c) {
return adaptee.retainAll(c);
}
public void clear() {
adaptee.clear();
}
}
We can use this as an adapter for any type of collection, for
example:
package com.maxoft.tjsn.util;
import java.util.LinkedList;
public class BetterCollectionTest {
public static void main(String[] args) {
BetterCollection names =
new BetterCollectionObjectAdapter(
new LinkedList(), String.class);
names.add("Wolfgang");
names.add("Leander");
names.add("Klaus");
names.add("Reinhard");
String[] nameArray = names.toArray();
for (String s : nameArray) {
System.out.println(s);
}
}
}
This solution works, but I dont like being exposed to changes
in the interface. Should Sun ever decide to add a new method
to the Collection interface, our class would not compile
anymore. Also, it is a lot of code to implement all those
methods, and to support the extended interfaces of List, Set,
SortedSet, etc., I would need to again write other adapters.
I know the chance of Sun changing java.util.Collection is
rather remote, but I did have this experience a few times
with the java.sql.Connection interface that I had adapted.
Dynamic Object Adapter using Dynamic Proxies
Now that we have seen the problem, let's examine the solution
based on dynamic proxies (with thanks to Dr Klaus
Wiederaenders for the idea). In the past I have written
several object adapters based on interfaces. Besides being
a lot of boring work, we experience pain new methods are
added to the interface. It can easily occur that your object
adapter then only works for one specific version of Java.
The first piece of the puzzle is a
DynamicObjectAdapterFactory. This contains the method
adapt, which takes an adaptee (the object that
we are adapting), the target (the interface that we want to
return) and the adapter (the object that contains methods
which override adaptee behaviour). We then create a dynamic
proxy of the target interface. The invocation handler gets
called whenever a method is called on the dymanic proxy.
Each of the declared methods in the adapter is put into a
map using an identifier that is based on the name and
parameter list of the method. This way, there does not have
to be an inheritance relationship between the adapter and the
target.
package com.maxoft.tjsn.util;
import java.lang.reflect.*;
import java.util.*;
public class DynamicObjectAdapterFactory {
public static T adapt(final Object adaptee,
final Class target,
final Object adapter) {
return (T) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{target},
new InvocationHandler() {
private Map adaptedMethods =
new HashMap();
// initializer block - find all methods in adapter object
{
Method[] methods = adapter.getClass().getDeclaredMethods();
for (Method m : methods) {
adaptedMethods.put(new MethodIdentifier(m), m);
}
}
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
try {
Method other = adaptedMethods.get(
new MethodIdentifier(method));
if (other != null) {
return other.invoke(adapter, args);
} else {
return method.invoke(adaptee, args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
});
}
private static class MethodIdentifier {
private final String name;
private final Class[] parameters;
public MethodIdentifier(Method m) {
name = m.getName();
parameters = m.getParameterTypes();
}
// we can save time by assuming that we only compare against
// other MethodIdentifier objectspublic boolean equals(Object o) {
MethodIdentifier mid = (MethodIdentifier) o;
return name.equals(mid.name) &&
Arrays.equals(parameters, mid.parameters);
}
public int hashCode() {
return name.hashCode();
}
}
}
I have used Java 5 generics in the dynamic object adapter
factory, but the solution would also work with JDK 1.3. Here
is how I would use the dynamic object adapter factory to
make an object adapter for my BetterCollection. Note that
this code generates a compiler warning, which we may safely
ignore. This type of code is what the @SuppressWarnings
annotation is meant for, but it seems to not have been
implemented in the compiler.
package com.maxoft.tjsn.util;
import java.lang.reflect.Array;
import java.util.Collection;
import static com.maxoft.tjsn.util.DynamicObjectAdapterFactory.*;
public class BetterCollectionFactory {
public static BetterCollection asBetterCollection(
final Collection adaptee, final Class valueType) {
return adapt(adaptee,
BetterCollection.class,
// this anonymous inner class contains the method that
// we want to adaptnew Object() {
public T[] toArray() {
return adaptee.toArray((T[]) Array.newInstance(
valueType, adaptee.size()));
}
// Whilst we are at it, we could also make it into a
// checked collection, see java.util.Collections for
// an example.public boolean add(T o) {
if (!valueType.isInstance(o))
throw new ClassCastException("Attempt to insert " +
o.getClass() +
" value into collection with value type " + valueType);
return adaptee.add(o);
}
// addAll left as an exercise for the reader :-)
});
}
}
Here is how we would use this BetterCollectionFactory. The
static imports in Java 5 save us some typing. We can simply
write asBetterCollection() and it then wraps our collection
with the BetterCollection using the adapter factory.
package com.maxoft.tjsn.util;
import java.util.*;
import static com.maxoft.tjsn.util.BetterCollectionFactory.*;
public class BestCollectionTest {
public static void main(String[] args) {
BetterCollection names = asBetterCollection(
new ArrayList(), String.class);
names.add("Wolfgang");
names.add("Leander");
names.add("Klaus");
names.add("Reinhard");
String[] nameArray = names.toArray();
for (String s : nameArray) {
System.out.println(s);
}
}
}
Next Thursday (19th May 2005) I am scheduled to present a
talk at the University of
Crete on Design Patterns, specifically on how to use dynamic
proxies in Java to autogenerate code for virtual proxies and
security proxies. Please send
me an email if you are in or near Iraklion, Crete,
Greece next week and would like to attend.
The dynamic proxy facilities in Java 1.4 and later are very powerful. The uses that you show here, using a hierarchy of objects for creating an adapter for overiding implementations is a very, very powerful way to replace a method in any object with a different implementation. Clearly it doesn't resolve the issue of data disparity between different objects that provide the methods, but there is some power, as you show, in this capability.
Combined with the fact that this dynamic proxy could be mobile code and the referred-to objects could be remove services, one can visualize how the smart-proxy mechanism empowers the developer to solve problems in unique and often powerful ways.
I found there are several restrictions on the parameters that can be passed to Proxy.getProxyClass:
* All of the Class objects in the interfaces array must represent interfaces, not classes or primitive types. * No two elements in the interfaces array may refer to identical Class objects. * All of the interface types must be visible by name through the specified class loader.
I was thinking about how to override method return type and you give me the answer, I have another problem you may already have the solution or idea for..
I would like to make a proxy of an interface with another interface that is identical to the adapted one except that I want to wrap all the return types.
I may mannualy write my wrapped interface, but I look for a solution to create my wrapped interface dynamicaly.