Created
July 16, 2018 18:05
-
-
Save matey-jack/844ff7455006bebb7b6880823cadbf9a to your computer and use it in GitHub Desktop.
Nice Duck Typing in Java: Wrap a class in an interface just as you would in Rust or Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package example; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Proxy; | |
/** | |
* Allows "duck typing" or dynamic invocation based on method signature rather | |
* than type hierarchy. In other words, rather than checking whether something | |
* IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck. | |
* | |
* To use first use the coerce static method to indicate the object you want to | |
* do Duck Typing for, then specify an interface to the to method which you want | |
* to coerce the type to, e.g: | |
* | |
* public interface Foo { | |
* void aMethod(); | |
* } | |
* class Bar { | |
* ... | |
* public void aMethod() { ... } | |
* ... | |
* } | |
* Bar bar = ...; | |
* Foo foo = DuckType.coerce(bar).to(Foo.class); | |
* foo.aMethod(); | |
* | |
* | |
*/ | |
public class DuckType { | |
private final Object objectToCoerce; | |
private DuckType(Object objectToCoerce) { | |
this.objectToCoerce = objectToCoerce; | |
} | |
private class CoercedProxy implements InvocationHandler { | |
public Object invoke(Object proxy, Method method, Object[] args) | |
throws Throwable { | |
Method delegateMethod = findMethodBySignature(method); | |
assert delegateMethod != null; | |
return delegateMethod.invoke(DuckType.this.objectToCoerce, args); | |
} | |
} | |
/** | |
* Specify the duck typed object to coerce. | |
* | |
* @param object the object to coerce | |
* @return | |
*/ | |
public static DuckType coerce(Object object) { | |
return new DuckType(object); | |
} | |
/** | |
* Coerce the Duck Typed object to the given interface providing it | |
* implements all the necessary methods. | |
* | |
* @param | |
* @param iface | |
* @return an instance of the given interface that wraps the duck typed | |
* class | |
* @throws ClassCastException if the object being coerced does not implement | |
* all the methods in the given interface. | |
*/ | |
public T to(Class iface) { | |
assert iface.isInterface() : "cannot coerce object to a class, must be an interface"; | |
if (isA(iface)) { | |
return iface.cast(objectToCoerce); | |
} | |
if (quacksLikeA(iface)) { | |
return generateProxy(iface); | |
} | |
throw new ClassCastException("Could not coerce object of type " | |
+ objectToCoerce.getClass() + " to " + iface); | |
} | |
private boolean isA(Class iface) { | |
return objectToCoerce.getClass().isInstance(iface); | |
} | |
/** | |
* Determine whether the duck typed object can be used with | |
* the given interface. | |
* | |
* @param Type of the interface to check. | |
* @param iface Interface class to check | |
* @return true if the object will support all the methods in the | |
* interface, false otherwise. | |
*/ | |
public boolean quacksLikeA(Class iface) { | |
for (Method method : iface.getMethods()) { | |
if (findMethodBySignature(method) == null) { | |
return false; | |
} | |
} | |
return true; | |
} | |
@SuppressWarnings("unchecked") | |
private T generateProxy(Class iface) { | |
return (T) Proxy.newProxyInstance(iface.getClassLoader(), | |
new Class[] { iface }, new CoercedProxy()); | |
} | |
private Method findMethodBySignature(Method method) { | |
try { | |
return objectToCoerce.getClass().getMethod(method.getName(), | |
method.getParameterTypes()); | |
} catch (NoSuchMethodException e) { | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment