我可能会问得太多,但Groovy似乎超级灵活,所以这里......
我想在类中定义一个方法,如下所示:
class Foo {
Boolean y = SomeOtherClass.DEFAULT_Y
Boolean z = SomeOtherClass.DEFAULT_Z
void bar(String x = SomeOtherClass.DEFAULT_X,
Integer y = this.y, Boolean z = this.z) {
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
并且只能提供如下某些参数:
def f = new Foo(y: 16)
f.bar(z: true) // <-- This line throws groovy.lang.MissingMethodException!
Run Code Online (Sandbox Code Playgroud)
我正在尝试提供灵活且类型安全的API,这就是问题所在.给定的代码不灵活,因为我必须传入(并且知道API的用户)默认值x才能调用该方法.以下是我想要的解决方案的一些挑战:
void bar(Map)签名,除非钥匙可以某种方式使类型安全.我意识到这一点,我可以在方法体中进行类型检查,但我正在努力避免这种冗余级别,因为我有许多这种"类型"的方法来编写.我可以为每个方法签名使用一个类 - 类似于:
class BarArgs {
String x = SomeOtherClass.DEFAULT_X
String y
String z
}
Run Code Online (Sandbox Code Playgroud)
并将它定义为:
void bar(BarArgs barArgs) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
并使用map构造函数以我想要的方式调用它:f.bar(z: true),但我的问题在于对象的默认值y.没有办法处理它(我知道),而不必在调用方法时指定它,如:f.bar(y: f.y, z: true).这对我的小样本来说很好,但我在一些方法上看20-30个可选参数.
欢迎任何建议(或问题,如果需要)!谢谢你看看.
有趣的问题.我已经解释了你的要求
我不确定5号,因为它没有明确指定,但它看起来就像你想要的那样.
据我所知,groovy中没有任何内置功能可以支持所有这些,但有几种方法可以让它以"简单易用"的方式工作.
想到的一种方法是创建专门的参数类,但只使用map作为方法中的参数.使用简单的超类或特性来验证和设置属性,获取每个方法的实际参数是一个单行.
这是一个特点和一些可以作为起点的例子:
trait DefaultArgs {
void setArgs(Map args, DefaultArgs defaultArgs) {
if (defaultArgs) {
setArgs(defaultArgs.toArgsMap())
}
setArgs(args)
}
void setArgs(Map args) {
MetaClass thisMetaClass = getMetaClass()
args.each { name, value ->
assert name instanceof String
MetaProperty metaProperty = thisMetaClass.getMetaProperty(name)
assert name && metaProperty != null
if (value != null) {
assert metaProperty.type.isAssignableFrom(value.class)
}
thisMetaClass.setProperty(this, name, value)
}
}
Map toArgsMap() {
def properties = getProperties()
properties.remove('class')
return properties
}
}
Run Code Online (Sandbox Code Playgroud)
使用此特性可以轻松创建专门的参数类.
@ToString(includePackage = false, includeNames = true)
class FooArgs implements DefaultArgs {
String a = 'a'
Boolean b = true
Integer i = 42
FooArgs(Map args = [:], DefaultArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
@ToString(includePackage = false, includeNames = true, includeSuper = true)
class BarArgs extends FooArgs {
Long l = 10
BarArgs(Map args = [:], FooArgs defaultArgs = null) {
setArgs(args, defaultArgs)
}
}
Run Code Online (Sandbox Code Playgroud)
还有一个使用这些参数的类:
class Foo {
FooArgs defaultArgs
Foo(Map args = [:]) {
defaultArgs = new FooArgs(args)
}
void foo(Map args = [:]) {
FooArgs fooArgs = new FooArgs(args, defaultArgs)
println fooArgs
}
void bar(Map args = [:]) {
BarArgs barArgs = new BarArgs(args, defaultArgs)
println barArgs
}
}
Run Code Online (Sandbox Code Playgroud)
最后,一个简单的测试脚本; 注释中方法调用的输出
def foo = new Foo()
foo.foo() // FooArgs(a:a, b:true, i:42)
foo.foo(a:'A') // FooArgs(a:A, b:true, i:42)
foo.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:42))
foo.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:42))
def foo2 = new Foo(i:16)
foo2.foo() // FooArgs(a:a, b:true, i:16)
foo2.foo(a:'A') // FooArgs(a:A, b:true, i:16)
foo2.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:16))
foo2.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000))
foo2.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:16))
def verifyError(Class thrownClass, Closure closure) {
try {
closure()
assert "Expected thrown: $thrownClass" && false
} catch (Throwable e) {
assert e.class == thrownClass
}
}
// Test exceptions on wrong type
verifyError(PowerAssertionError) { foo.foo(a:5) }
verifyError(PowerAssertionError) { foo.foo(b:'true') }
verifyError(PowerAssertionError) { foo.bar(i:10L) } // long instead of integer
verifyError(PowerAssertionError) { foo.bar(l:10) } // integer instead of long
// Test exceptions on missing properties
verifyError(PowerAssertionError) { foo.foo(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.bar(nonExisting: 'hello') }
verifyError(PowerAssertionError) { foo.foo(l: 50L) } // 'l' does not exist on foo
Run Code Online (Sandbox Code Playgroud)