Often is the case that you really do want a method to return multiple values as different client require different parts back. This is often simpler than creating multiple methods; but it might not be worth creating a bean class for. For simple two value return types you can use a nice generic version of Pair; but things get harder if you have more value. (You can use a holder class to simulate IN/OUT parameters, but it doesn't result in pretty code) I was working on a method that needed to return four values depending on the calling context so I turned to a type safe heterogeneous container from the Effective java book:
/**
* An implementation of a type safe heterogeneous container, taken from
* effective java
*/
public class THC
{
final private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>();
public <T> void put(Class<T> key, T value)
{
if (key==null) throw new NullPointerException("Key cannot be null");
container.put(key, value);
}
public <T> T get(Class<T> key)
{
// Return the value of think the thunk if required
//
Object object = container.get(key);
return key.cast(object);
}
}
It doesn't provide full compile time safety; but it is close enough for our purposes. You can write code that looks like:
THC thc = getSSLConfiguration(...); SSLContext sslContext = thc.get(SSLContext.class); // or in another case KeyManager kms[] = thc.get(KeyManager[].class); TrustManager tms[] = thc.get(TrustManager[].class);
So this is fine, except as you see from the example above that depending on the context I need different subset of the objects. Some are derived from others so I could end up doing extra work to populate a part of the return value that is never used.
I decided to look at using a Thunk as a way to optimize the work done on the return value of this method. So I modified the THC class to include a new public interface Thunk with one method that takes a reference to the original container.
/**
* An implementation of a type safe heterogeneous container, taken from
* effective java
*/
public class THC
{
/**
* Allow the client to provide a derived value
* @param <T>
*/
public interface Thunk<T>
{
public T get(THC that);
}
/**
* Place a value in the structure that has yet to be derived.
* @param key
* @param value The value to be thought of in the future
*/
public <T> void put(Class<T> key, Thunk<T> value)
{
if (key==null) throw new NullPointerException("Key cannot be null");
container.put(key, value);
}
public <T> T get(Class<T> key)
{
// Return the value of thinking the thunk if required
//
Object object = container.get(key);
if (object instanceof Thunk)
{
object = ((Thunk)object).get(this);
container.put(key, object);
}
return key.cast(object);
}
}
You find that because Thunk takes a parameter of the THC the adapter classes can be constants, this saves a little bit on object creation. So I can write something like:
static final private Thunk<SSLContext> contextT = new Thunk<SSLContext>()
{
public SSLContext get(THC that)
{
SSLContext c = SSLContext.getInstance("SSL");
c.init(that.get(KeyManager[].class)
that.get(TrustManager[].class), null);
return c;
}
}
static final private Thunk<SSLSocketFactory> factoryT = new Thunk<SSLSocketFactory>()
{
public SSLSocketFactory get(THC that)
{
return that.get(SSLSocketFactory.class).getSSLSocketFactory();
}
}
public THC getSSLConfiguration(...)
{
...
THC thc = new THC();
thc.put(KeyManager[].class, kms);
thc.put(TrustManager[].class, tms);
thc.put(SSLContext.class, contextT);
thc.put(SSLSocketFactory.class, factoryT);
}
So I can now write my client code as follows and the SSLContext and SSLSocketFactory are created on demand:
THC context = getSSLConfiguration(...); SSLSocketFactory sf = context.get(SSLSocketFactory.class);
Perhaps not a technique that is applicable to all situations; but interesting all the same.
Update: Some time later it occurred that the THC interface if of course not particularly pretty, so an obvious extension is to have THC return a Proxy based on a interface that can be used to define the return types. Not written this yet; but it would be pretty trivial to do.

No comments:
Post a Comment