w Groovy zastąpienia sposobu zdefiniowanego w interfejsie stosując metaklasą jest uszkodzony. W tym przypadku metoda exec
jest zdefiniowana w klasie Project
, która jest interfejsem. Od GROOVY-3493 (zgłoszone pierwotnie w 2009 roku):
"Cannot override methods via metaclass that are part of an interface implementation"
Obejście problemu
invokeMethod
przechwytuje wszystkie metody i może pracować. To jest przesada, ale działa. Gdy nazwa metody pasuje do exec
, przekazuje połączenie do obiektu mySpecialInstance
. W przeciwnym razie jest przekazywana do delegata, a mianowicie do istniejących metod. Dzięki invokeMethod delegation i Logging All Methods do wprowadzania na tym.
// This intercepts all methods, stubbing out exec and passing through all other invokes
this.project.metaClass.invokeMethod = { String name, args ->
if (name == 'exec') {
// Call special instance to track verifications
mySpecialInstance.exec((Closure) args.first())
} else {
// This calls the delegate without causing infinite recursion
MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args)
return metaMethod?.invoke(delegate, args)
}
}
Działa to dobrze, z wyjątkiem, że może pojawić się wyjątki o „złym liczbą argumentów” lub „nie może powoływać się na metody xxxxx pustego obiektu”. Problem polega na tym, że powyższy kod nie radzi sobie z wymuszaniem argumentów metody. W przypadku parametru project.files(Object... paths)
argumenty dla metody invokeMethod powinny mieć postać [['path1', 'path2']]
. ALE, w niektórych przypadkach jest wywołanie files(null)
lub files()
, więc argumenty dla invokeMethod okazują się odpowiednio [null]
i []
, które nie spełniają oczekiwań, ponieważ oczekują one [[]]
. Wyprodukowanie wyżej wymienionych błędów.
Następujący kod rozwiązuje to tylko dla metody files
, ale to wystarczyło dla moich testów jednostkowych. Nadal chciałbym znaleźć lepszy sposób na wymuszanie typów lub najlepiej zastąpienie jednej metody.
// As above but handle coercing of the files parameter types
this.project.metaClass.invokeMethod = { String name, args ->
if (name == 'exec') {
// Call special instance to track verifications
mySpecialInstance.exec((Closure) args.first())
} else {
// This calls the delegate without causing infinite recursion
// https://stackoverflow.com/a/10126006/1509221
MetaMethod metaMethod = delegate.class.metaClass.getMetaMethod(name, args)
logInvokeMethod(name, args, metaMethod)
// Special case 'files' method which can throw exceptions
if (name == 'files') {
// Coerce the arguments to match the signature of Project.files(Object... paths)
// TODO: is there a way to do this automatically, e.g. coerceArgumentsToClasses?
assert 0 == args.size() || 1 == args.size()
if (args.size() == 0 || // files()
args.first() == null) { // files(null)
return metaMethod?.invoke(delegate, [[] as Object[]] as Object[])
} else {
// files(ArrayList) possibly, so cast ArrayList to Object[]
return metaMethod?.invoke(delegate, [(Object[]) args.first()] as Object[])
}
} else {
// Normal pass through
return metaMethod?.invoke(delegate, args)
}
}
}
Nie jestem w 100% pewny, że zadziała, ale możesz użyć tej techniki w innym poście na moim blogu na temat nadpisywania metod, ale wciąż używając oryginalnej implementacji: http://naleid.com/blog/2009/06/01/groovy-metaclass-overriding-a-method-while-using-the-old-implementation To może zostać zablokowane przez problem interfejsu, który zidentyfikowałeś chociaż ... Jeśli tak, to rozwiązanie, które dla mnie wygląda jak dobre. –
Dzięki Ted, ten sam problem z interfejsem uniemożliwia jego działanie. Interesują mnie twoje przemyślenia na temat typów wymuszających. Prosty kod kończy się niepowodzeniem dla 'project.files (Object ... paths)' dla 'files (null)' i 'files()'. Czy istnieje lepszy sposób wymuszania typów parametru varargs w sposób ogólny? Zapraszam do edytowania mojego posta z ulepszeniami. – brunobowden