Reading — step 1 of 5
Learn
~1 min readTraits and DSL Composition
Groovy traits are like Java interfaces but with implementation. Like Scala traits, like Kotlin interfaces with default methods, like Ruby modules.
trait Greetable {
abstract String name() // must be implemented
String hello() {
"Hello, ${name()}"
}
}
class Person implements Greetable {
String n
Person(String n) { this.n = n }
String name() { n }
}
new Person("Ada").hello() // "Hello, Ada"
Multiple traits — diamond OK:
trait Logging {
void log(String msg) { println "[${this.class.simpleName}] $msg" }
}
trait Caching {
Map cache = [:]
def cached(String key, Closure compute) {
cache.computeIfAbsent(key, { compute() })
}
}
class Service implements Logging, Caching {
String process(String input) {
log("processing $input")
cached(input, { input.toUpperCase() })
}
}
Groovy resolves diamond conflicts in implementation order (last-trait-wins).
Stateful traits — they CAN have fields (unlike Java interfaces):
trait Counter {
int count = 0
void increment() { count++ }
}
The field is added to each implementing class.
Stackable trait pattern — combine multiple traits' behavior on a method:
trait Filtered {
String process(String s) {
s.replace("bad", "***")
}
}
trait Logged {
String process(String s) {
println "processing: $s"
Filtered.super.process(s) // or super.process(s) — depending on order
}
}
class Filter implements Filtered, Logged {}
Traits vs categories:
- Traits: declared at class level, type-safe, mixed in at compile time
- Categories (
use(Category)): scope-limited, dynamic, similar to monkey patching
For most modern Groovy code, prefer traits — they're cleaner.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…