- 论坛徽章:
- 0
|
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
![]()
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.*;
import static com.maxoft
public class BetterArrayList extends 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 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:
package com.maxoft.tjsn.util;
import java.util.Collection;
public interface BetterCollection extends Collection {
T[] toArray();
}
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 BetterCollectionObjectAdapter
implements 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 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 objects
public 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..*;
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 adapt
new 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
[email=heinz@javaspecialists.co.za?subject=Talk in Crete]send me an email[/email]
if you are in or near Iraklion, Crete, Greece next week and would like to attend.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/42545/showart_331615.html |
|