I recently ran into a case where I was violating the DRY principle by having to encode part of a string every time I added to it. After some digging I found the solution: Groovy Categories.
A bit of Groovy background
A couple key features of Groovy are that everything is an object and that operators are just syntactic sugar for calling methods on the objects. What’s cool about this is that with Groovy you can override the default behavior of these operators for certain classes. For example, 4 + 2
in Groovy really means 4.plus(2)
How to implement/override operators in Groovy
Groovy allows you to override a LOT of stuff, including final methods and operators. If I want to override a method on the String
class, all I need to do is:
String.metaClass.plus {
//It is a special term for the argument passed (if there is only 1)
String stringToEncode = it
//delegate is a special term for the caller of the method
delegate << URLEncoder.encode(it, 'UTF-8')
}
String first = "my delegate "
String second = "twitter @eriwen"
//prints "my delegate twitter+%40eriwen"
println first + second
Notice that it (intentionally) only encodes the second String
. Check out the list of operators and associated methods. This is great, but I need to restrict this to code blocks I so choose. That’s where Groovy Categories come in.
Overriding with Categories
Groovy Categories allow you to add functionality to any class at runtime. This means that you can add in methods to final classes (like java.lang.String
). In this case I just want to override a method instead of adding one.
class MyUtils {
//Takes 2 java.lang.Strings and returns a String - TYPES ARE OPTIONAL
static String plus(String first, String second) {
//Concatenates the Strings and returns
first << URLEncoder.encode(second, encoding)
}
}
//my other class in another file...
import MyUtils
class MyOtherClass {
String first = "first"
String second = "to be encoded"
use (MyUtils) {
// This will now encode value
list << first + second
}
}
Note that Groovy defaults to GString
s, so you can’t just use def actuallyMyString
.
How would you make this even more Groovy? Enjoy!