|
The Java Specialists' Newsletter
Issue 034 2001-10-24
Category:
Language
Java version: Generic Types with Dynamic Decoratorsby Dr. Heinz M. Kabutz
Welcome to the 34th issue of "The Java(tm) Specialists'
Newsletter", in which we look at how we can apply the dynamic
decorators to produce type-safe iterators dynamically. I know
that some of you will be tempted to write to me how "that will
all be in JDK 1.5" or "here are 1001 reasons why your construct
is not useful". The purpose of this newsletter is to make
you think, please don't forget that. It is not to provide you
with cut & dry solutions to your problems.
Thanks to Dr. Christoph Jung and Bruce Eckel for the ideas that
spawned this newsletter. Some of these examples will probably
be featured in the "Thinking in Patterns" book that Bruce is busy
writing at the moment. I can't wait for that to get
published ...
1486 members are currently subscribed from 55 countries
Would you like to really understand Java concurrency? Join us for an
in-depth study of how threading works in Java. During the course,
you will learn how to write correct and fast multi-threaded Java code.
Please
click here if you would like to learn more. Generic Types with Dynamic Decorators
Proxy vs. Decorator
What is the difference between a Proxy and a
Decorator?
If we look at the structure of these patterns in the GoF book, we
see that the Proxy has an association from the Proxy class to the
RealSubject class (actually, it would be more versatile to have
an association between Proxy and the Subject interface).
Essentially the structure in Java code would look like this:
interface Subject {
public void request();
}
class RealSubject implements Subject {
public void request() { /* do something */ }
}
class Proxy implements Subject {
private Subject realSubject;
Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
public void request() {
/* do something, then */
realSubject.request();
}
}
The Decorator pattern's structure has an aggregation from the
Decorator class to the Component. The structure in Java code
would look like this:
interface Component {
public void operation();
}
class ConcreteComponent implements Component {
public void operation() {
/* do something */
}
}
class Decorator implements Subject {
private Component component;
Decorator(Component component) {
this.component = component;
}
public void operation() {
/* do something, then */
component.operation();
}
}
class ConcreteDecorator extends Decorator {
ConcreteDecorator(Component component) {
super(component);
}
public void anotherOperation() {
/* decorate the other operation in some way, then call the
other operation() */
operation();
}
}
If we now change the names of the classes to A, B, C and the
method to f(), Proxy becomes:
interface A {
public void f();
}
class B implements A {
public void f() { /* do something */ }
}
class C implements A {
private A a;
C(A a) {
this.a = a;
}
public void f() {
/* do something, then */
a.f();
}
}
And if we do the same to Decorator, that becomes:
interface A {
public void f();
}
class B implements A{
public void f() { /* do something */ }
}
class C implements A {
private A a;
C(A a) {
this.a = a;
}
public void f() {
/* do something, then */
a.f();
}
}
class D extends C {
D(A a) {
super(a);
}
public void g() {
/* decorate the other operation in some way, then call the
other operation() */
f();
}
}
Oops, by looking at the structure alone, we see that there is not
that much difference between Proxy and Decorator. We also have
to look at the intents:
Proxy: Provide a surrogate or placeholder for another
object to control access to it.
Decorator: Attach additional responsibilities to an object
dynamically. Decorators provide a flexible alternative to
subclassing for extending functionality.
We have to look at the structure and the intent and the
applicability together if we want to figure out which pattern we
are looking at. Even then, since a lot of the patterns are very
similar, we can get into heated debates about this. Bruce Eckel
and I exchanged some interesting emails trying to decide whether
what you are about to see is the Decorator, Adapter or Proxy.
If you got lost in this discussion, you are missing out on one
of the most powerful techniques developed in Software Engineering
in the last decade, that being Design Patterns. I've developed
a new classroom-based course that will teach you how to recognise
and apply Object-Oriented Design Patterns.
Please look at our website for more information.
The answer to the question of "what is the difference between a
Proxy and a Decorator" lies mainly in the intent.
We intend to use a Decorator to extend the interface of a object
dynamically, whereas a Proxy is intended to provide a surrogate
object for the real object.
Enough of a Design Patterns lesson. Let's get to the meat of
today's newsletter.
Generic Types with Dynamic Decorators
Bruce Eckel sent me an email asking if I had some nice examples
for using the Dynamic Proxies of JDK 1.3. I passed the question
on to my friend Christoph Jung who sent an interesting reply.
He said that they use dynamic proxies quite a lot in order to
handle type-safe collections. The issues addressed actually
reminded me of generic collections, due in JDK 1.5 hopefully.
Let's remember how we used collections in the past. Say we had a
stray animal pound in which we had collections of dogs and cats.
This code is what the "client" code of our dogs and cats would
use, i.e. in the client code we have to keep on doing type
casting down to the correct type while iterating through the
collection.
public class Cat {
private final String species;
public Cat(String species) { this.species = species; }
public String toString() { return species; }
public void meow() { System.out.println(this + ": Meow"); }
}
public class Dog {
private final String species;
public Dog(String species) { this.species = species; }
public String toString() { return species; }
public void bark() { System.out.println(this + ": Woof"); }
}
import java.util.*;
public class Pound {
private Collection dogs;
private Collection cats;
public Pound(Dog[] dogs, Cat[] cats) {
this.dogs = Arrays.asList(dogs);
this.cats = Arrays.asList(cats);
}
public void makeNoise() {
Iterator dog_it = dogs.iterator();
while(dog_it.hasNext()) {
((Dog)dog_it.next()).bark(); // we have to downcast!
}
Iterator cat_it = cats.iterator();
while(cat_it.hasNext()) {
((Cat)cat_it.next()).meow(); // we have to downcast!
}
}
}
Wouldn't it be nice if we could have a collection for dogs and
a collection for cats so that we would not have to worry about
downcasting? Indeed, but Java does not support generics yet, I
hear some of you say.
How could we improve our situation by abusing dynamic proxies?
(For a detailed newsletter on dynamic proxies, please refer to
Issue #5) What if we
decided on a naming convention within our objects that we want to
collect? Let's say we change Dog to be the following:
public class Dog {
private final String species;
public Dog(String species) { this.species = species; }
public String toString() { return species; }
public void bark() { System.out.println(this + ": Woof"); }
public static interface Collection extends java.util.Collection {
Iterator dogIterator();
}
public static interface Iterator extends java.util.Iterator {
Dog nextDog();
}
}
}
Our client code could therefore use Dog.Collection
and call method dogIterator(), which would return
a Dog.Iterator with the specialised method
nextDog(). This means we never have to downcast the
anonymous objects to Dog!
So, how do we actually make an instance of this
Dog.Collection? Simple, dynamic proxies ... with a
twist. First of all, I write a GenericFactory class (the Fowler
Factory Method pattern, not the GoF). This GenericFactory has a
makeCollection() method where I pass in an instance
of the collection backing the Dog.Collection (this is where the
adapter pattern comes in), and the type that I want to support.
It is assumed that the type you pass in (e.g. Dog.class) has an
inner interface called Collection with a method
dogIterator() and another inner interface called
Iterator that contains a method
nextDog().
If these are not found we throw an IllegalArgumentException.
import java.lang.reflect.Proxy;
import java.util.*;
public class GenericFactory {
public static Collection makeCollection(Collection backing,
Class type) {
GenericCollection gen = new GenericCollection(backing, type);
return (Collection)Proxy.newProxyInstance(
gen.getTypeCollectionClass().getClassLoader(),
new Class[] { gen.getTypeCollectionClass() },
gen);
}
/* please ignore makeIterator for now ... */
public static Iterator makeIterator(Iterator backing, Class type) {
GenericIterator gen = new GenericIterator(backing, type);
return (Iterator)Proxy.newProxyInstance(
gen.getTypeIteratorClass().getClassLoader(),
new Class[] { gen.getTypeIteratorClass() },
gen);
}
}
The GenericCollection class looks something like this. What it
does is intercept any calls to the Dog.Collection proxy and checks
if it is the dogIterator() method. If it is, it uses the
GenericFactory to make a Dog.Iterator.
import java.lang.reflect.*;
import java.util.*;
public class GenericCollection implements InvocationHandler {
private final Collection backing;
private final Class type;
private final Class typeCollection;
public GenericCollection(Collection backing, Class type) {
this.backing = backing;
this.type = type;
typeCollection = discoverCollection();
}
/** Find the correct inner class Collection interface.
@throws IllegalArgumentException if inner interface Collection
not found */
private Class discoverCollection() {
Class[] innerClasses = type.getClasses();
for (int i=0; i<innerClasses.length; i++) {
if (innerClasses[i].getName().equals(
type.getName() + "$Collection")) {
return innerClasses[i];
}
}
throw new IllegalArgumentException(
"Class does not contain inner Collection interface");
}
public Class getTypeCollectionClass() {
return typeCollection;
}
/** This is the meat of the GenericCollection. It finds any
method with type name followed by iterator and hijacks it. */
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equalsIgnoreCase(type.getName() + "Iterator")) {
return GenericFactory.makeIterator(backing.iterator(), type);
}
return method.invoke(backing, args);
}
}
In order to make the picture complete, we also have to see the
code that makes the Iterators. The GenericIterator looks very
similar to the GenericCollection. I am sure we could have a
GenericType from which both of those classes could inherit and
which could contain common functionality. However, I was writing
the code while trying to pacify a crying baby. Not that easy,
seeing that I cleverly changed all the keys around on my notebook
so that now the only way to use it is to touchtype. Almost
dropped it a few times - the notebook, that is ;-)
import java.lang.reflect.*;
import java.util.*;
public class GenericIterator implements InvocationHandler {
private final Iterator backing;
private final Class type;
private final Class typeIterator;
public GenericIterator(Iterator backing, Class type) {
this.backing = backing;
this.type = type;
typeIterator = discoverIterator();
}
/** Find the correct inner class Iterator interface.
@throws IllegalArgumentException if inner interface Iterator
not found */
private Class discoverIterator() {
Class[] innerClasses = type.getClasses();
for (int i=0; i<innerClasses.length; i++) {
if (innerClasses[i].getName().equals(
type.getName() + "$Iterator")) {
return innerClasses[i];
}
}
throw new IllegalArgumentException(
"Class does not contain inner Iterator interface");
}
public Class getTypeIteratorClass() {
return typeIterator;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equalsIgnoreCase("next" + type.getName())) {
return backing.next();
}
return method.invoke(backing, args);
}
}
How do you use this code? Let's have another look at the newly
improved Pound class. You will notice that I am making the
collections in the constructor of the Pound, and after that I use
those collections directly. Normally, the collections would
contain Value Objects from your database, and they would be made
dynamically by your framework.
import java.util.*;
public class Pound {
private Dog.Collection dogs;
private Cat.Collection cats;
public Pound(Dog[] dogs, Cat[] cats) {
this.dogs = (Dog.Collection)GenericFactory.makeCollection(
new LinkedList(Arrays.asList(dogs)), Dog.class);
this.cats = (Cat.Collection)GenericFactory.makeCollection(
new ArrayList(Arrays.asList(cats)), Cat.class);
}
public void makeNoise() {
Dog.Iterator dog_it = dogs.dogIterator();
while(dog_it.hasNext()) {
/* no more downcasting! */
dog_it.nextDog().bark();
}
Cat.Iterator cat_it = cats.catIterator();
while(cat_it.hasNext()) {
/* no more downcasting! */
cat_it.nextCat().meow();
}
}
}
}
We can test that in our PoundTest class by making a new Pound and
calling the makeNoise method.
public class PoundTest {
public static void main(String[] clargs) throws ClassNotFoundException {
Pound spca = new Pound(
new Dog[] {
new Dog("Alsation"),
new Dog("Bulldog"),
new Dog("PavementSpecial") },
new Cat[] {
new Cat("RussianBlue"),
new Cat("DeadBounce") });
spca.makeNoise();
}
}
With output of:
Alsation: Woof
Bulldog: Woof
PavementSpecial: Woof
RussianBlue: Meow
DeadBounce: Meow
Oh, by the way, Cat would look like this:
public class Cat {
private final String species;
public Cat(String species) { this.species = species; }
public String toString() { return species; }
public void meow() { System.out.println(this + ": Meow"); }
public static interface Collection extends java.util.Collection {
Iterator catIterator();
}
public static interface Iterator extends java.util.Iterator {
Cat nextCat();
}
}
Yes, I know this is only a small step towards all the things you
can do with generics in C++, and hopefully in JDK 1.5. However,
it has made me think in a different way about what we can do with
dynamic "proxies".
Was that a Proxy or a Decorator?
Back to our earlier discussion of whether this was a Proxy or a
Decorator. I say this is more of a Decorator than anything else,
as we are dynamically and transparently extending the
functionality of existing classes without subtyping. The
interesting part is that the Decorator class is not actually a
class per se, it is a naming convention.
Disclaimer
I know that the dynamic decorators shown in this newsletters have
some deficiencies at the moment. For example, it is possible to
add a Cat to a collection of Dogs without an exception being
thrown immediately. In a production-quality system you should
obviously check that.
throw new ExerciseForTheReaderException();
Last Words
Yes, I know that I am harping on about Design Patterns. However,
without a solid understanding of Design Patterns you will never
be a real Java Specialist. So, don't delay, pop me an email
today ... :-)
Language Articles
Related Java Course
Discuss at The Java Specialist Club
|