Groovy Copying / Combining MetaMethods From Multiple Objects

293 Views Asked by At

I have two classes. At runtime, I want to "clone" the methods of one object, over to another. Is this possible? My failed attempt using leftshift is shown below.

(Note: I also tried currMethod.clone() with the same result.)

class SandboxMetaMethod2 {
    String speak(){
        println 'bow wow'
    }
}

class SandboxMetaMethod1{

  void leftShift(Object sandbox2){
      sandbox2.metaClass.getMethods().each{currMethod->
          if(currMethod.name.contains("speak")){
              this.speak()
              this.metaClass."$currMethod.name" = currMethod
              this.speak()
          }
      }
  }

  String speak(){
    println 'woof'
  }
}

class SandboxMetaMethodSpec extends Specification {
    def "try this"(){
        when:
        def sandbox1 = new SandboxMetaMethod1()
        def sandbox2 = new SandboxMetaMethod2()
        sandbox1 << sandbox2


        then:
        true
    }

}


//Output
woof
speak
woof

Per Request, I am adding background as to the goal / use case:

It's very much like a standard functional type of use case. In summary, we have a lot of methods on a class which applies to all of our client environments (50-100). We apply those to process data in a certain default order. Each of those methods may be overridden by client specific methods (if they exist with the same method name), and the idea was to use the approach above to "reconcile" the method set. Based on the client environment name, we need a way to dynamically override methods.

Note: Overriding methods on the metaclass is very standard (or should i say, it's the reason the amazing capability exists). And it works if my method exists as text like String currMethod = "{x-> x+1}", then i just say this.metaClass."$currMethodName" = currMethod. My challenge in this case is that my method is compiled and exists on another class, rather than being defined as text somewhere.

The goal of having all the custom methods compiled in client-specific classes at build time was to avoid the expense of compilation of these dynamic methods at runtime for each calculation, so all client-specific methods are compiled into a separate client-specific JAR at build time. This way also allows us to only deploy the client-specific code to the respective client, without all the other clients calculations in some master class.

I hope that makes sense.

New Approach, in Response to Jeremie B's suggestion:

Since I need to choose the trait to implement by name at runtime, will something like this work:

String clientName = "client1"
String clientSpeakTrait = "${clientName}Speak"

trait globalSpeak {
    String speak() {
        println 'bow wow'
    }
}

trait client1Speak {
    String speak() {
        println 'woof'
    }
}

def mySpeaker = new Object().withTraits globalSpeak, clientSpeakTrait
1

There are 1 best solutions below

8
On BEST ANSWER

A basic example with Traits :

trait Speak {
    String speak() {
        println 'bow wow'
    }
}

class MyClass {

}

def instance = new MyClass()
def extended = instance.withTraits Speak

extended.speak()

You can choose which trait to use at runtime :

def clientTrait = Speak
def sb = new Object().withTraits(clientTrait)

sb.speak()

And dynamically load the trait with a ClassLoader :

def clientTrait = this.class.classLoader.loadClass "my.package.${client}Speak"
def sb = new Object().withTraits(clientTrait)