Как изменить порядок делегирования метода в Groovy? - PullRequest
0 голосов
/ 22 апреля 2020

Моя цель - изменить порядок делегирования методов в Groovy, чтобы я мог динамически переопределять методы в Groovy объекте.

У меня есть класс с именем Weapon, определенный ниже:

class Weapon {
    Prefix prefix

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

, где prefix является экземпляром этого класса:

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 динамически добавлять методы во время выполнения.

The специфицировано c желаемое поведение заключается в том, что любой метод, вызываемый 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, поэтому мне не известны все его возможности. Сначала я думал, что смогу сделать это, просто переопределив метод invokeMethod в Weapon, но Groovy закончил тем, что нашел метод до того, как invokeMethod когда-либо был вызван (я предполагаю, что он был найден в метаклассе или классе).

Единственное решение, которое я могу придумать, - это создать собственный метакласс для 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)
    }
}

Однако, это не сработало, и каждый вызов метода из Weaponprefix или без него) вместо этого возвращал ноль. Я сделал ошибку в своей реализации, есть ли лучший способ сделать это, или это даже невозможно?

1 Ответ

1 голос
/ 23 апреля 2020

вы можете использовать метакласс по умолчанию для достижения требуемого поведения

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'
...