In Groovy we can write classes and write an implementation for methods we don't even know exist. We must implement the method methodMissing() which is invoked when other methods cannot be found. Because methodMissing() is invoked each time another method cannot be found, it can be expensive, but we can use the metaClass property to cache the new method. Next time the cached version is used.
class LanguageList {
def list = ['Java', 'Groovy', 'Scala']
// Set metaClass property to ExpandoMetaClass instance, so we
// can add dynamic methods.
LanguageList() {
def mc = new ExpandoMetaClass(LanguageList, false, true)
mc.initialize()
this.metaClass = mc
}
def methodMissing(String name, args) {
// Intercept method that starts with find.
if (name.startsWith("find")) {
def result = list.find { it == name[4..-1] }
// Add new method to class with metaClass.
this.metaClass."$name" = {-> result + "[cache]" }
result
} else {
throw new MissingMethodException(name, this.class, args)
}
}
}
def languages = new LanguageList()
assert 'Groovy' == languages.findGroovy()
assert 'Scala' == languages.findScala()
assert 'Java' == languages.findJava()
assert !languages.findRuby()
assert 'Groovy[cache]' == languages.findGroovy()
assert 'Scala[cache]' == languages.findScala()
assert 'Java[cache]' == languages.findJava()


3 comments:
Don't need to check if the method exists before caching it. MethodMissing is called only when the method doesn't exists, so groovy does this check for us.
See this: http://groovyconsole.appspot.com/view.groovy?id=41003
I don't understand why it behaves differently when methodMissing is implemented inside the class.
@Felipe Cypriano: the different behavior happens because of the metaClass type when defining the methodMissing in the class or with the metaClass property. When we use LanguageList.metaClass.methodMissing the metaClass is an ExpandoMetaClass instance. In the class methodMissing definition the metaClass is a MetaClassImpl instance. To add methods dynamically the metaClass must be of type ExpandoMetaClass to make it all work. I changed the sample to create a ExpandoMetaClass object and assign it to the metaClass property to the dynamically attached methods are used.
The "problem" is in the default implementation of metaClass property, we just change it to ExpandoMetaClass and we're done.
Great code.
Post a Comment