AnnoTRAITions brings the flexibility of PHP 5.4's Traits to Java by way of an annotation and an associated processor.
Lovingly borrowed from the PHP documentation on the subject, "Traits are a mechanism for code reuse in single inheritance".
In practice, I've found Traits most usefull as a way to add functionality to an existing Class, without interrupting the logical flow of a hierarchy.
By way of an example, say you have a few Classes;
public class Apple {
...
}
public class Orange {
...
}
And some snippets of code for shaing a representation of an Object to Facebook or Twitter;
public void postToTwitter(Object toPost) {
String postMessage = "Hey, Twitter, check out, '" + toPost.toString() + "'";
...
}
public void postToFacebook(Object toPost) {
String postMessage = "Hey, Facebook, check out, '" + toPost.toString() + "'";
...
}
Now, if you wanted Apple
and Orange
to both benifit from all the hard work you put in to write the postToTwitter
and postToFacebook
methods, you'd have a few options;
-
Change the methods to be static and wrap them in a class. Then call something like;
Apple apple = new Apple(); Twitter.postToTwitter(apple);
-
Have
Apple
andOrange
at some point extend a common Class that has both methods;Apple apple = new Apple(); apple.postToTwitter(apple);
-
Traits add an additional option. You can say that all
Apple
s use a Trait calledTweetable
. They allow you to inject thepostToTwitter
mechanism into your Class without altering your hierarchy, but while still maintaining a relationship betweenApple
andTweetable
.
-
Add the repository to your
build.gradle
file;repositories { mavenCentral() maven { url 'https://raw.github.com/iainconnor/annoTRAITion/master/maven/' } }
-
And add the dependency;
dependencies { compile 'com.iainconnor:annotraition:1.0.0' }
-
Add the annotation processor.
If you're writing an Android appplication, I'd recommend using the android-apt plugin. Otherwise, the gradle-apt plugin should work. Either way, you'll need to add the processor as an additional dependency;
dependencies { apt 'com.iainconnor.annotraition:processor:1.0.1' compile 'com.iainconnor:annotraition:1.0.0' }
-
Download the
.jar
for the latest version from this repository. -
And the
.jar
for the latest processor from this repository. -
Add both to your porject and find out how to add these flags to the javac compilation;
-proc:only -processor com.iainconnor.annotraition.processor.Processor
-
Create your Trait. Returning to the example from above, you'll need to add the
@Trait
annotation, have it extendcom.iainconnor.annotraition.MasterTrait
, and implement the Constructor from that Class;@Trait public class Tweetable extends com.iainconnor.annotraition.MasterTrait { public Tweetable(Object traitedObject) { super(traitedObject); } public void postToTwitter() { String postMessage = "Hey, Twitter, check out, '" + traitedObject.toString() + "'"; ... } }
Note that
MasterTrait
adds anObject traitedObject
. You can use this Object to get the instance that has the Trait applied to it. -
Have your Classes that want to use a Trait do so through the
@Use
annotation;@Use (Tweetable.class) public class Apple { ... }
-
Or your Classes that want to use more than one Trait do so through the
@Uses
annotation;@Uses ({@Use (Tweetable.class), @Use (Facebookable.class)}) public class Orange { ... }
-
In the rest of your code, use the traited versions of your Classes. After the annotation processor has run, two new classes will be built with the names
_AppleTraited
and_OrangeTraited
;_AppleTraited apple = new _AppleTraited(); apple.postToTwitter();
-
Whats really going on here?
Good question. I find the easiest way to understand what's at work is to look at the output of our example above. This is the contents of
_OrangeTraited.java
after being annotated;// Generated by AnnoTRAITion. // Do not edit, your changes will be overridden. public class _OrangeTraited extends Orange { protected Facebookable facebookable; protected Tweetable tweetable; public _OrangeTraited() { super(); this.facebookable = new Facebookable(this); this.tweetable = new Tweetable(this); } /** * Passes through to `Facebookable.postToFacebook`. */ public void postToFacebook() { this.facebookable.postToFacebook(); } /** * Passes through to `Tweetable.postToTwitter`. */ public void postToTwitter() { tweetable.postToTwitter(); } }
As you can see, local instances of the Traits have been added to this subclass, and any methods in those Traits are wrapped by proxy methods.
-
I heard that annotations slow down your code execution!
Many annotations use run-time reflection, which can slow down your application, which is why AnnoTRAITions runs exclusively at compile time. No additional code except the example you see above is added to your run-time processing.
Love it? Hate it? Want to make changes to it? Contact me at @iainconnor or iainconnor@gmail.com.