Skip to content
Alex Vigdor edited this page Sep 28, 2018 · 2 revisions

Using traits in Groovity

Traits are a language feature of Groovy that allow the composition of classes using multiple inheritance. A trait can define both fields and methods, but it is implemented and can be tested for using interface semantics. Think of a trait like an interface and an concrete base class mixed together. It is quite different from java default interface methods, which cannot define or directly access any object fields.

Usage patterns for traits in Groovity are identical to how they would be used in a standard Groovy project, but there are some caveats to keep in mind in a Groovity setting. Hopefully over time we can whittle this list down.

  • @CompileStatic annotations can be used on trait methods, BUT they will not be able to compile if they rely on any built-in groovity functions like run, load or tag
  • Trait methods are invisible and untracked by groovity statistics
  • there is no dedicated documentation ability for traits (though they do appear in inheriting class docs)
  • compiling a trait at runtime requires a "force compile all" for dependencies to get updated properly

Traits are currently the ONLY way to introduce any compile-time dependencies between physically separate groovity files; standard groovity module interactions are mediated by groovy's dynamic nature to avoid class-level linkages. Because of this the use of traits can lead to certain headaches not typically encountered in groovity. Caveat Emptor!

Examples:

trait HasAuthors{
	String[] authors

	public Collection<IsPerson> getAuthors(){
		if(authors){
			return load('/types/factory')('author',authors)
		}
	}

	public Writable getByline(){
		<~
		<g:set var="delim" value=""/>
		<g:write value="By"/>
		<g:each var="author" in="${getAuthors()}">
			<g:write value="${delim} ${author.name} (${author.twitter})" />
			<g:set var="delim" value=","/>
		</g:each>
		~>
	}
}
trait HasReferences{
	IsReference[] references

	public Collection<IsReference> getReferences(){
		references
	}

	public Collection<IsReferent> resolveReferences(){
		if(references){
			return load('/types/factory')(references)
		}
	}

	public void setReferences(IsReference... references){
		this.references = references
	}

	public void setReferences(List<Map> references){
		this.references = references.collect{ ref -> new DefaultReference([ id:ref.get('id'), type:ref.get('type') ]) } as IsReference[]
	}
}

class DefaultReference implements IsReference{}
class Story implements IsReferent, HasAuthors, HasReferences{
	String body

	public int getWordCount(){
		body.tokenize().size()
	}

}