如何更改 Groovy 中方法委托的顺序?

How can I change the order of method delegation in Groovy?

我的目标是更改 Groovy 中方法委托的顺序,以便我可以动态覆盖 Groovy 对象中的方法。

我有一个名为 Weapon 的 class,定义如下:

class Weapon {
    Prefix prefix

    String method() {
        'Called from Weapon'
    }
}

其中 prefix 是此 class 的实例:

class Prefix {
    final Map<String, Closure> methods = [:]

    void propertyMissing(String name, Closure value) {
        if (value != null) {
            methods[name] = value
        }
        else {
            methods.remove(name)
        }
    }

    def methodMissing(String name, args) {
        if (!methods.containsKey(name)) {
            throw new MissingMethodException(name, Prefix, args)
        }
        methods.(name)(args)
    }
}

此设计允许 Prefix 在运行时动态添加方法。

特定 所需的行为是 Weapon 调用的任何方法将首先在 prefix 中搜索它(如果它不为空)。如果它不存在,那么 Weapon 将使用其正常行为搜索该方法(如此处所示:https://docs.groovy-lang.org/latest/html/documentation/core-metaprogramming.html#_runtime_metaprogramming)。

这是所需行为的一个简单示例:

Weapon w = new Weapon()
Prefix p = new Prefix()

p.method = { 'Called from Prefix' }
w.prefix = p

assert w.method() == 'Called from Prefix'

w.prefix = null

assert w.method() == 'Called from Weapon'

我对 Groovy 的元编程还很陌生,所以我一点也不了解它的所有功能。起初,我以为我可以通过简单地覆盖 Weapon 中的 invokeMethod 方法来做到这一点,但是 Groovy 最终在 invokeMethod 被调用之前找到了该方法(我假设它是在metaclass 或 class).

我能想到的唯一解决方案是为 Weapon 创建一个自定义元类,它首先检查 Weapon 的前缀。我的实现如下:

class WeaponMetaClass {
    WeaponMetaClass(MetaClass metaClass) {
        super(metaClass)
    }
    Object invokeMethod(Object object, String methodName, Object[] args) {
        if (object instanceof Weapon && object.prefix != null) {
            try {
                return object.prefix.(methodName)(args)
            }
            catch (MissingMethodException ignored) {
            }
        }
        return super.invokeMethod(object, methodName, args)
    }
}

但是,这没有用,来自 Weapon(有或没有 prefix)的每个方法调用都返回 null。我是不是在我的实现中犯了错误,是否有更好的方法,或者这是不可能的?

您可以使用默认元类来实现所需的行为

class Weapon {
    void setPrefix(Map m){
        this.metaClass=null                     //reset to default class behavior
        if(m)m.each{k,v-> this.metaClass[k]=v}  //redefine methods/properties from map
    }

    String method() {
        'Called from Weapon'
    }
}

def w = new Weapon()
//you could wrap prefix with custom class - however it's just a map container
def prefix=[
    method: {'Called from Prefix'}
]


assert w.method()=='Called from Weapon'

w.prefix = prefix
assert w.method()=='Called from Prefix'

w.prefix = null
assert w.method()=='Called from Weapon'