2

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
solvingJ
  • 1,321
  • 1
  • 19
  • 30
  • Have you tried traits? http://docs.groovy-lang.org/next/html/documentation/core-traits.html – tim_yates Feb 27 '16 at 08:49
  • 1
    why do you want to do such a thing? what's your requirement? tbh, it doesn't look like a good idea – Jérémie B Feb 27 '16 at 10:27
  • Yes, I use traits extensively, what an amazing feature. – solvingJ Feb 27 '16 at 14:39
  • I think i see your reason for using traits. I can take advantage of Traits native conflict handling to let the second class override the first. This just might work! Just never used the conflict handling, so didn't think to design with it. – solvingJ Feb 27 '16 at 15:00
  • Note that when a method is added via the meta class, the *source* is a closure, not text. So, you could turn your methods into closure properties in some class. I'd still try using a trait first. – Emmanuel Rosa Feb 27 '16 at 15:12
  • Emmanuel, Thanks for the response, while i'm leaning toward traits, for posterity, can you show how I would achieve that in my example? – solvingJ Feb 27 '16 at 15:38

1 Answers1

1

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)
Jérémie B
  • 10,611
  • 1
  • 26
  • 43
  • Thank you! Actually, with the traits I've used, I've never implemented using the withTraits method either, so I'll consider that. And in your example, if "MyClass" had a speak method already, would the trait override in this case? – solvingJ Feb 27 '16 at 16:41
  • yes, with `withTraits`, the method will be overrided – Jérémie B Feb 27 '16 at 16:47
  • I just updated the original post with a question about doing this more dynamically. Can you let me know if thats possible? or something like that? – solvingJ Feb 27 '16 at 16:50
  • yes, it works, i have added it in my answer, but clientTrait should be a `Class`, not a String – Jérémie B Feb 27 '16 at 16:54
  • Sorry, I think something is still missing. The client name comes in as a string. This client name will be part of the trait name. Can we dynamically specify the trait name to implement based on a string? – solvingJ Feb 27 '16 at 17:15
  • I would remove the mixin. It just confuses the answer, and don't think it would be recommended if deprecated right? – solvingJ Feb 27 '16 at 17:16
  • you can load a class dynamically by String with a classloader : `classLoader.loadClass "${client}Speak"` – Jérémie B Feb 27 '16 at 17:19
  • Wow, thank you... THAT is a little trickier than I expected. I really appreciate you taking the time to answer. – solvingJ Feb 27 '16 at 17:25