如何使用Python风格的kwargs和默认值进行Groovy方法签名?

And*_*ndy 4 groovy

我可能会问得太多,但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个可选参数.

欢迎任何建议(或问题,如果需要)!谢谢你看看.

Ste*_*nar 6

有趣的问题.我已经解释了你的要求

  1. 该类应具有一组默认属性.
  2. 每个方法都应该有一组默认参数.
  3. 方法默认值覆盖类默认值.
  4. 每个方法都可以有其他参数,不存在于类中.
  5. 方法参数不应修改类实例.
  6. 提供的参数需要检查类型.

我不确定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)