7

Groovy exposes an ExpandoMetaClass that allows you to dynamically add instance and class methods/properties to a POJO. I would like to use it to add an instance method to one of my Java classes:

public class Fizz {
    // ...etc.
}

Fizz fizz = new Fizz();
fizz.metaClass.doStuff = { String blah -> fizz.buzz(blah) }

This would be the equivalent to refactoring the Fizz class to have:

public class Fizz {
    // ctors, getters/setters, etc...

    public void doStuff(String blah) {
        buzz(blah);
    }
}

My question:

Does this add doStuff(String blah) to only this particular instance of Fizz? Or do all instances of Fizz now have a doStuff(String blah) instance method?

If the former, how do I get all instances of Fizz to have the doStuff instance method? I know that if I made the Groovy:

fizz.metaClass.doStuff << { String blah -> fizz.buzz(blah) }

Then that would add a static class method to Fizz, such as Fizz.doStuff(String blah), but that's not what I want. I just want all instances of Fizz to now have an instance method called doStuff. Ideas?

1 Answers1

3

Firstly, when you add to the main class of Fizz, its instances do not get the method as the instances have already been factored out and added to memory.

So one way of approaching this is to use the method signature from the original class. Therefore instead of

fizz.doStuff(blah)

call the method of the class. Therefore

fizz.&doStuff(blah)

This gets the method signature from the originating class, but uses the attributes from the instance. However as you can imagine, since its calling the originating class, this is a slightly heavy call.

Now one alternative of pushing out to every instance is to make the instances ExpandoMetaClass instances of Fizz. Hence...

Fizz.metaClass.doStuff = {return "blah"}
fizz = new Fizz()
Fizz.metaClass.doOtherStuff = {return "more blah"}
assert fizz.doOtherStuff() == "more blah"

Hope this helps

UPDATE:

Full code example:

class Fizz{
}

Fizz.metaClass.doOtherStuff = {return "more blah"}
def fizz = new Fizz()
assert fizz.doOtherStuff() == "more blah"

def fizz1 = new Fizz()
assert fizz1.doOtherStuff() == "more blah"
dmahapatro
  • 49,365
  • 7
  • 88
  • 117
bythe4mile
  • 642
  • 7
  • 16
  • Thanks @mugdho (+1) - but you sort of threw me off guard with the whole "&doStuff()" stuff... can you provide a full code example demonstrating how to use ExpandoMetaclass to attach `doStuff` as an instance method to all instances of `Fizz`? Thanks again! –  Nov 06 '13 at 21:23
  • +1. In order to add static methods to metaClass use `Fizz.metaClass.'static'.fooStuff = {.....}` – dmahapatro Nov 06 '13 at 21:28
  • 2
    @TicketMonster Mark the difference, he is adding method to the metaClass of `Fizz` class instead of `fizz` instance. That makes it possible to have `doStuff` available for all instances of Fizz. – dmahapatro Nov 06 '13 at 21:29
  • Thanks @dmahapatro (+1) - but still not seeing the "forest" through the "trees". A full code example would really pull it all together for me... –  Nov 06 '13 at 21:46
  • So basically we need to update an object calling the metaClass method to convert its adaptee into from a regular MetaClassImpl to an ExpandoMetaClass. An adaptee is the the attribute that decides what runs the implementation (If that makes sense :-)) – bythe4mile Nov 06 '13 at 21:46
  • 1
    @TicketMonster There you go. See the update to mugdho's answer. – dmahapatro Nov 06 '13 at 21:50
  • 1
    Just what I was about to add as well :). Thanks dmahapatro! – bythe4mile Nov 06 '13 at 21:53
  • Thanks @mugdho (+1) - that makes perfect sense! Last followup question: can I assume that if I wanted `doOtherStuff` to accept an integer parameter, that I could write it like so: `Fizz.metaClass.doOtherStuff = { int x -> return "more blah"}`? Then I could invoke it like so: `fizz.doOtherStuff(7);`? Am I correct? Thanks again! –  Nov 06 '13 at 22:46