Background:
For a while now I’ve wanted a delegate-like (C# delegates – think of them as basically type-safe function pointers) feature to use in Java (and don’t mention closures or dynamic languages please, I know it will make my whole post obsolete
), and I tried several attempts to simulate them, but didn’t quite get it right. I learnt about funky uses of the proxy pattern from my ex-team lead (very neat person, hey Steve!) in various projects. Then Java 5 came along…
No wait, before that, one day as I was messing around with cglib (I call it an AOP enabling tech through proxies – go proxy!), I noticed that they provided a dynamic delegate creator, in the cglib sources, see: net.sf.cglib.reflect.MethodDelegate – quoting from Apache Avalon‘s Delegate class, a delegate is defined as an interface with a single method. Note that Avalon has been marked as a closed project – but I don’t think it had anything to do with the delegate definition, phew.
delegates4j is Born:
Anyway, while that was neat (and backward compatible with pre-Java 5 stuff, hmph), it required a method name as a string, not a very big deal, but not quite what I wanted either, I wanted something a little more declarative, and something perhaps simpler, and a little more typesafe; and not requiring an external class enhancer (yes, maybe I just wanted to reinvent the wheel and not use cglib to do it). Annotations to the rescue! So! Keeping in mind that a delegate is an interface with a single method, let’s look at what I have (might want to paste this into an editor if it’s too annoying to read):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | package net.namingcrisis.delegates4j.core; import static net.namingcrisis.delegates4j.core.DelegateFactory.cdg; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class DelegateFactoryBasicTest { // Basic case, also a sampling of how the API is used. @Before public void setUp() throws Exception { } // Delegate type interface, only a single method. // Think of it as a function pointer in disguise // (C# of course supports delegates natively) private static interface IStringProcessor { String process(String s); } // A strategy class with different string processing routines // Does NOT extend StringProcessor, nor do the method names have to match, // this is the point. private static class GString { /* * It's a joke here, but GTK actually has a * gstring type */ private static final String UPPER_CASE = "upper case"; private static final String PRAYER = "prayers"; private static final String EMPTY = "nada"; @Implements(clazz = IStringProcessor.class, mode = UPPER_CASE) public String toUpperCase(String s) { return s.toUpperCase(); } @Implements(clazz = IStringProcessor.class, mode = PRAYER) public String toPrayer(String s) { return "Hallelujah! " + s; } @Implements(clazz = IStringProcessor.class, mode = EMPTY) public String toEmptyString(String s) { return ""; // :P } } // A test client of StringProcessor, assume it to be a simple visitor // Returns a new array of processed strings public String[] printStrings(final String[] strings, IStringProcessor proc) { final String[] res = new String[strings.length]; for (int i = 0; i < strings.length; i++) res[i] = proc.process(strings[i]); return res; } @Test public void testCreateDelegate() { final String[] strings = new String[] { "you", "and", "i" }; final GString gs = new GString(); // Dynamically create delegate instance, cdg is a static import // meaning "create delegate", deliberately keeping the name // short and unintrusive. // cdg(interfacetype, mode, implementationInstance) final IStringProcessor upperCaseProcessor = cdg(IStringProcessor.class, GString.UPPER_CASE, gs); String[] res = printStrings(strings, upperCaseProcessor); Assert.assertTrue(res.length == strings.length); for (int i = 0; i < strings.length; i++) Assert.assertEquals(res[i], gs.toUpperCase(strings[i])); final IStringProcessor prayerProcessor = cdg(IStringProcessor.class, GString.PRAYER, gs); res = printStrings(strings, prayerProcessor); Assert.assertTrue(res.length == strings.length); for (int i = 0; i < strings.length; i++) Assert.assertEquals(res[i], gs.toPrayer(strings[i])); final IStringProcessor emptyProcessor = cdg(IStringProcessor.class, GString.EMPTY, gs); res = printStrings(strings, emptyProcessor); Assert.assertTrue(res.length == strings.length); for (int i = 0; i < strings.length; i++) Assert.assertEquals(res[i], gs.toEmptyString(strings[i])); } } |
A couple of things (noted in comments):
- The implementation class did NOT implement the delegate’s interface (or the delegate rather, “delegate interface” is redundant going by the above definition!), it merely provided compatible methods, whose names did not have to match.
- The use of mode, is of course a string, and perhaps not very typesafe either, but it is exclusively there to support the scenario where multiple similar methods implement different behaviour, allowing you to have several strategies in a class, very handy for quick things. Probably not a good idea for larger reusable strategies. If you didn’t have it, just set it as an empty string (at the moment it’ll NPE-out otherwise
) - This test doesn’t illustrate it, but to keep things single and fast (method resolution is done at runtime after all), it matches declared methods only, nothing inherited is usable – an interface that extends another but has no method will error out as a delegate type, a class with a superclass annotated method overridden but not annotated, also errors out. (The class bit is redundant, annotations are not inherited anyway, so).
- Finally – also not illustrated by this test, delegates4j supports Java 5 covariance, where the return type of the implementor can be narrower than that of the delegate type.
Hopefully, it is simple enough to use – my initial attempts had multiple steps, but they tried to do more too. An improvement to this is possibly somehow caching resolved methods for reuse keyed by the annotation detail for example, in practice I don’t know if this is worth it yet.
The project is available as Maven downloadable jar (licensed under Apache License v2.0) in my repository, including sources, and tests to better illustrate usage/limitations (and of course, test that it works!). Set the dependency as follows: groupId = net.namingcrisis, artifactId = delegates4j, version = 0.5.
For the curious, under the hood, the only real magic is the use of the JDK Dynamic Proxy to implement the delegate and pass on the request to the supplied instance, and reflection for method resolution – the source is there anyway
. If you do use it, hate it, try it out, etc. drop me a line anyway
.
– Kamal
Some thoughts:
I was thinking if a use case such that one method can implement multiple interfaces happens often enough to support something like:
And if so, what of the semantics of mode, would you leave it at a single mode either way, or would you prefer grouping them together (I think annotations allow this):
2
3
4
5
6
7
@Context(clazz=IFace1.class, mode="foo"),
@Context(clazz=IFace1.class, mode="bar"),
@Context(clazz=IFace2.class, mode="foo")
}
)
void foo() { ... }
And so on.
After discussing with Yogi, my .NET brother from the dark side, God Bless his soul (he has yet to give me a web address), we both agree modes are not elegant. An alternative is to have 2 versions of cdg(), one that takes a method name, which implicitly becomes a mode of operation, and the mode removed entirely, and the other cdg() without a method name which gets the first @Implement-ed method. Currently if you had 2 methods annotated with the same @Implements mode and clazz, the first encountered one (by reflection, whatever order it uses), is used. Yogi feels for safety it should error out if more than one method with the same @Implements spec is found, tend to agree.
DelegateInfo -> Implementing java.lang.reflect.Method
It is important to note that we would cache immutable types, not instances at all.
I’ve thought of various tricks, such as what Mock frameworks do to record methods, but all have their limitations, if you use JDK dynamic proxies, then your implementing class must also implement its own interface so the proxy can use it, or if you use cglib, your implementing class cannot be final because cglib internally creates a similar type by overriding your original methods, i.e. by inheritance. All kinda annoying – I imagine the cglib restriction is something we’ve grown used to because we use Hibernate, but still.
Ah I so wish we had a language level construct: Method m = Foo.bar, grrr, I wonder what it clashes with, if you had overloaded methods, could we incorporate the parameters say with a new keyword say, Method m = getreference Foo.bar(“bar”) – where the ‘getreference’ keyword specifies a compile time method search rather than an execution – I wonder if this could be implemented using some form of erasure where m is resolved at compile time. It would also allow ‘getreference’ to be used with other reflective operations, getting fields, etc., not just methods. Obviously I have not considered the various language constructs that this would clash with, just thinking out loud.
Comment by Kamal — February 26, 2009 @ 10:38 pm
cdg?! come on you can do better than that, i think in this instance it’s perhaps better to give it a more meaningful name than a cdg class
but a neat concept all the same, thumbs up
Comment by paul li — March 14, 2009 @ 1:15 pm
Hey Paul,
Thanks for the comments and taking the time to look at this
.
cdg is a static method name, the class as you see in the import list is called DelegateFactory. The point behind the short method name (cdg == create delegate) is to reduce verbosity, the method name is almost intended to be a pseudo keyword. I agree that it’s non-descriptive initially, but considering it to be a mnemonic doesn’t seem too bad
.
Comment by Kamal Advani — March 14, 2009 @ 5:32 pm