do you ever had that problem, that a class asks for a certain interface that your client class hasn’t implemented, although all of the interfaces required methods are present in that class?
to make things clear, here’s a little example:first of all, here’s a ‘typical’ interface (i deliberately use the prefix I to highlight that this one is an interface):
public interface IBar { public String echo( String s ); }
and here’s a class, that will work with that interface:
public class BarCollaborator { public void speakTo( IBar bar ){ System.out.println( "hello bar - " + bar.echo( "hello" ) ); } }
now you have a class Foo, that offers (by incident) a method with the same signature like the one required by the interface:
public class Foo { public String echo(String s) { return "foo " + s; } }
although Foo accomplishes the contract of IBar, we can’t apply an instance of Foo to BarCollaborator, because of the incompatible types.
if you use a language like ruby, were a class doesn’t rely on a certain type of a collaborator but rather on its interface (the methods an object offers – most of you will know that feature as ‘duck typing’: if it walks like a duck and if it talks like a duck than we will treat it like a duck), you certainly never will have a problem with that constellation.
but java is a different beast, using statically typed classes, where the type stays relevant during runtime. imagine if we could say something like this:
‘apply this instance of Foo to BarCollaborator and make it appear as if it implements interface IBar’
in essence – something like
‘use myFoo as IBar.class’
DynamicProxy to the rescue
well, this is possible – to a certain extend – with the help of a DynamicProxy. A DynamicProxy will ‘mimic’ a collection of interfaces (in this case we only need to mimic a single interface. in our conrete situation interface IBar ) to a client who will perform against those interfaces (in our concrete situation class BarCollaborator). when a client calls a method on that interface at runtime, the Proxy is called instead, having the chance to intercept the call and do whatever is necessery to complete it. regarding our context, we only try to detect the corresponding method on the target object (the instance of class Foo) and invoke it.
now here’s the first simple implementation:
public class InterfaceBridgeProxy implements InvocationHandler { // factory method public static <T> T as( Class<T> asInterface, Object bean ){ return (T) Proxy.newProxyInstance( bean.getClass().getClassLoader(), new Class[]{asInterface}, new InterfaceBridgeProxy( bean ) ); } private Object proxee = null; public InterfaceBridgeProxy( Object proxee ){ this.proxee = proxee; } public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { Method toInvoke = findMatchingMethod( method ); return toInvoke != null? toInvoke.invoke( proxee, args ) : null; } private Method findMatchingMethod( Method method ){ try { return proxee.getClass().getDeclaredMethod( method.getName(), method.getParameterTypes() ); } catch( Exception e) { return new RuntimeException(e); } } }
now we can call BarCollaborator and apply an instance of Foo. using a static import for InterfaceBridgeProxy’s static method as() will make the code a little more concise:
public static void main(String[] args) { BarClient barClient = new BarClient(); Foo foo = new Foo(); barClient.speakTo( as( IBar.class, foo ) ); }
of course we could apply a generic method as() directly to class Foo …
public class Foo { public String echo(String s) { return "foo " + s; } public <T> T as( Class<T> interfaceMask ){ return InterfaceBridgeProxy.as( interfaceMask ); } }
… making the example a little more readable:
... barClient.speakTo( foo.as( IBar.class ) )
the implemented functionality is general. now you are able to mimic an arbitrary interface for your collaborators, as long as your class offers the same methods required by the interface.
next time we will go one step further and see how to extend this mechanism to even map methods of your class to an interface which even has slightly different method names as long as the rest of the signature is matching.
stay tuned … :o)
November 22, 2007 at 10:16 pm
In case you’re interested, there is an existing open source library, JRetrofit, which does the thing you were talking about:
http://www.laughingpanda.org/projects/jretrofit/
November 22, 2007 at 10:16 pm
thanks for your comment. i’ll take a look … :o)