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

如何使用Python风格的KWARG和默认值进行Groovy方法签名?,groovy,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) {

我可能要求太多了,但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) {
        // ...
    }

}
并且只能提供某些论点,如:

def f = new Foo(y: 16)
f.bar(z: true) // <-- This line throws groovy.lang.MissingMethodException!
并将其定义为:

void bar(BarArgs barArgs) {
    // ...
}
并使用映射构造函数使用我想要的方式调用它:
f.bar(z:true)
,但我的问题在于对象的默认值为
y
。如果不在调用方法时指定它,就无法处理(据我所知),如:
f.bar(y:f.y,z:true)
。这对于我的小示例来说很好,但我正在查看一些方法上的20-30个可选参数


欢迎提出任何建议(或问题,如果需要的话)!谢谢您的关注。

有趣的问题。我已经这样解释了你的要求

  • 该类应具有一组默认属性
  • 每个方法都应该有一组默认参数
  • 方法默认值覆盖类默认值
  • 每个方法都可以有额外的参数,而这些参数在类中不存在
  • 方法参数不应修改类实例
  • 需要检查提供的参数的类型
  • 我不确定数字5,因为它没有明确指定,但 看起来这就是你想要的

    据我所知,groovy中没有内置的东西来支持这一切, 但是有几种方法可以使它以“简单易用”的方式工作

    想到的一种方法是创建专门的参数类,但是 仅将映射用作方法中的参数。用一个简单的超类 或trait来验证和设置属性,它是一个获取 每个方法的实际参数

    以下是一个特征和一些可以作为起点的示例:

    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
        }
    }
    
    有了这个特性,创建专门的参数类就很容易了

    @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)
        }
    }
    
    以及使用以下参数的类:

    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
        }
    }
    
    最后,一个简单的测试脚本;注释中方法调用的输出

    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
    

    回答得很好。作为一个额外的好处,关于如何记录这样一个构造的一些建议如何?方法上的JavaDoc注释会为每个关键字参数设置一个
    @param
    ,还是应该将文档留给参数类?
    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