MOP方法合成

方法注入(method injection):编写代码时知道想要添加到一个或多个类中的方法的名字。利用方法注入,可以动态地向类中添加行为。也可以向任意数目的类中注入一组实现某一特定功能的可复用方法。可以通过使用分类,使用ExpandoMetaClass或Groovy的Mixin工具来注入方法。

方法合成(method synthesis):想在调用时动态地确定方法的行为。Groovy的invokeMethod()、methodMissing()和GroovyInterceptable对于方法合成非常有用。

合成的方法可能直到调用时才会作为独立的方法存在。

使用methodMissing合成方法

class Person {
    def work() { "working..." }

    def plays = ['Tennis', 'VolleyBall', 'BasketBall']

    def methodMissing(String name, args) {
        println "methodMissing called for $name"
        def methodInList = plays.find { it == name.split('play')[1] }
        if (methodInList) {
            def impl = { Object[] vargs -> "playing ${name.split('play')[1]}..." }
            Person instance = this
            instance.metaClass."$name" = impl
            impl(args)
        } else {
            throw new MissingMethodException(name, Person.class, args)
        }
    }
}

jack = new Person()

println jack.work()
println jack.playTennis()
println jack.playVolleyBall()
println jack.playBasketBall()
println jack.playTennis()

try {
    jack.playPolitics()
} catch (Exception e) {
    println "Error: " + e
}

对于实现了GroovyInterceptable的对象,调用该对象上的任何方法,都会调用到invokeMethod()。所以,又可以用以下方式:

class Person implements GroovyInterceptable {
    def work() { "working..." }
    def plays = ['Tennis', 'VolleyBall', 'BasketBall']
    def invokeMethod(String name, args) {
        println "intercepting call for $name"
        def method = metaClass.getMetaMethod(name, args)
        if (method) {
            method.invoke(this, args)
        } else {
            metaClass.invokeMethod(this, name, args)
        }
    }

    def methodMissing(String name, args) {
        println "methodMissing called for $name"
        def methodInList = plays.find { it == name.split('play')[1] }
        if (methodInList) {
            def impl = {
                Object[] vargs -> "playing ${name.split('play')[1]}..."
            }
            Person instance = this
            instance.metaClass."$name" = impl
            impl(args)
        } else {
            throw new MissingMethodException(name, Person.class, args)
        }
    }
}

使用ExpandoMetaClass合成方法

在我们无法对源码进行修改的时候,我们也就无法使用methodMissing进行合成方法。这时,我们可以使用ExpandoMetaClass合成方法。

class Person {
    def work() { "working..." }
}

Person.metaClass.methodMissing = { String name, args ->
    def plays = ['Tennis', 'VolleyBall', 'BasketBall']

    println "methodMissing called for $name"
    def methodInList = plays.find { it == name.split('play')[1] }

    if (methodInList) {
        def impl = { Object[] vargs ->
            "playing ${name.split('play')[1]}..."
        }
        Person.metaClass."$name" = impl
        impl(args)
    } else {
        throw new MissingMethodException(name, Person.class, args)
    }
}

jack = new Person()

println jack.work()
println jack.playTennis()
println jack.playTennis()
try {
    jack.playPolitics()
} catch (Exception e) {
    println "Error: " + e
}

为代码添加上invokeMethod拦截方法:

Person.metaClass.invokeMethod = { String name, args ->
    println "intercepting call for ${name}"
    def method = Person.metaClass.getMetaMethod(name,args)
    if(method){
        method.invoke(delegate,args)
    }else{
        Person.metaClass.invokeMissingMethod(delegate, name, args)
    }
}

为具体的实例合成方法

class Person{}

def emc = new ExpandoMetaClass(Person)
emc.methodMissing = {String name,args ->
    "I'm Jack of all trades... I can $name"
}
emc.initialize()

def jack = new Person()
def paul = new Person()

jack.metaClass = emc

println jack.sing()
println jack.dance()
println jack.juggle()

try{
    paul.sing()
}catch (Exception e){
    println e
}