Skip to content

Instantly share code, notes, and snippets.

@culmat
Last active February 24, 2017 10:37
Show Gist options
  • Save culmat/bee7afc71840a8035c5e5753d44e0de8 to your computer and use it in GitHub Desktop.
Save culmat/bee7afc71840a8035c5e5753d44e0de8 to your computer and use it in GitHub Desktop.
if it walks like a duck and talk like a duck ...
package common;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DuckType implements InvocationHandler {
public static class Let {
private Object object;
public Let(Object object) {
this.object = object;
}
private void checkQuackLike(Object object, Class<?> interfaceClass) throws InvocationTargetException {
Class<?> candclass = object.getClass();
StringBuilder sb = new StringBuilder(candclass.getName()+" does not implement\n");
int initialLength = sb.length();
for (Method method : interfaceClass.getMethods()) {
try {
candclass.getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
sb.append(" - "+method+"\n");
}
}
if(sb.length()> initialLength) throw new InvocationTargetException(null, sb.toString());
}
public <T> T be(Class<T> interfaceClass) throws InvocationTargetException {
checkQuackLike(object, interfaceClass);
return implement(object, interfaceClass);
}
public <T> T wannaBe(Class<T> interfaceClass) {
return implement(object, interfaceClass);
}
private <T> T implement(Object object, Class<T> interfaceToImplement) {
return (T) Proxy.newProxyInstance(interfaceToImplement.getClassLoader(),
new Class[] { interfaceToImplement}, new DuckType(object));
}
}
public static class Does {
private Class<? extends Object> candclass;
public Does(Object object) {
candclass = object.getClass();
}
public boolean quackLike(Class<?> interfaceClass) {
for (Method method : interfaceClass.getMethods()) {
try {
candclass.getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
return false;
}
}
return true;
}
}
public static Let let(Object object) {
return new Let(object);
}
public static Does does(Object object) {
return new Does(object);
}
protected DuckType(Object object) {
this.object = object;
this.objectClass = object.getClass();
}
protected Object object;
protected Class objectClass;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method realMethod = objectClass.getMethod(method.getName(), method.getParameterTypes());
if (!realMethod.isAccessible()) {
realMethod.setAccessible(true);
}
return realMethod.invoke(object, args);
}
}
package common;
import static common.DuckType.does;
import static common.DuckType.let;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationTargetException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class DuckTypeTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
public interface Duck {
String walk();
String talk();
}
class Chicken {
public String walk() { return "Chicken walks"; }
}
class Hen {
public String walk() { return "Hen walks"; }
public String talk() { return "Hen talks"; }
}
private Chicken chicken;
private Hen hen;
@Before
public void setUp() throws Exception {
chicken = new Chicken();
hen = new Hen();
}
@Test
public void chickenDoesNotQuackLikeDuck() {
assertFalse(does(chicken).quackLike(Duck.class));
}
@Test
public void chickenCannotBeDuck() throws Exception {
expectedEx.expect(InvocationTargetException.class);
expectedEx.expectMessage(getClass().getName()+"$Chicken does not implement\n" +
" - public abstract java.lang.String "+getClass().getName()+"$Duck.talk()");
let(chicken).be(Duck.class);
}
@Test
public void letChickenWannBeDuck() {
Duck duck = let(chicken).wannaBe(Duck.class);
assertEquals("Chicken walks", duck.walk());
}
@Test
public void duckTypedHashCode() {
assertEquals(chicken.hashCode(), let(chicken).wannaBe(Duck.class).hashCode());
}
@Test
public void duckTypedEquals() {
assertEquals(let(chicken).wannaBe(Duck.class), chicken);
}
@Test
public void henDoesQuackLikeDuck() {
assertTrue(does(hen).quackLike(Duck.class));
}
@Test
public void letHenBeDuck() throws InvocationTargetException {
Duck duck = let(hen).be(Duck.class);
assertEquals("Hen walks", duck.walk());
assertEquals("Hen talks", duck.talk());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment