Dynamically provide a default implementation of an instance method from within a Groovy Mixin

Suppose you have some common Groovy functionality you are sharing with a few classes via mixin. You want to provide a default implementation of a given method without resorting to inheritance.

Suppose you have some common Groovy functionality you are sharing with a few classes via mixin. You want to provide a default implementation of a given method without resorting to inheritance. The following listing is a basic class:
  class Common {<br>    def doThis() {<br>      // this<br>    }<br><br>    def doThat() {<br>      // that<br>    }<br><br>    /**<br>     * Provides a default doOther() if one does not exist<br>     */<br>    def methodMissing(String name, args) {<br>      def mixinOwner = metaClass.owner<br>      if (name  'doOther' && !args) {<br>        mixinOwner.class.metaClass."${name}" << {-><br>          // Default implementation of doOther()<br>        }<br>        return invokeMethod(name, args)<br>      }<br>      throw new MissingMethodException(name, this.class, args)<br>    }<br>  }

Another class will build upon Common and add some additional functionality:

  @Mixin(Common)<br>  class Extended {<br>    def doExtendedStuff() {<br>      // extended stuff<br>    }<br>  }

Thus Extended supports the following operations:

def extended = new Extended()<br>extended.doThis()<br>extended.doThat()<br>extended.doOther()<br>extended.doExtendedStuff()doOther()

is implemented via Common.methodMissing(). methodMissing() is called by Groovy when a nonexistent method is invoked on an object, providing an opportunity to handle the call. In our case, we provide a default implementation:

    def methodMissing(String name, args) {<br>      if (name  'doOther' && !args) {<br>        mixinOwner.class.metaClass."${name}" << {-><br>          // Default implementation of doOther()<br>        }<br>        return invokeMethod(name, args)<br>      }<br>      throw new MissingMethodException(name, this.class, args)<br>    }

We check that the name of the missing method is ‘doOther’ and that there are no args; this allows for method overloading by ensuring that doOther() and doOther(args) are not handled the same way. We then add the missing method to all instances of Extended by appending a closure containing the default implementation of doOther() to the metaclass of the mixin owner’s class, so all instances will get it. This ensures that subsequent calls to Extended.doOther() will not cause methodMissing() to be invoked, effectively caching the method for better performance. This has the same effect as declaring the method on Extended at compile time. Finally, we invoke the method and return the result. If we have some other missing method, we ensure that it throws MissingMethodException so Groovy will report the error.

Suppose Extended needs to provide its own implementation of doOther:

  @Mixin(Common)<br>  class Extended {<br>    def doExtendedStuff() {<br>      // extended stuff<br>    }<br><br>    def doOther() {<br>      // extended other stuff<br>    }<br>  }Common.methodMissing()

will not be invoked for Extended.doOther(), which effectively overrides the mixed-in method and allows Extended to supply its own implementation.

Jed Prentice