如何使用Python样式的kwargs和默认值来执行Groovy方法签名?

时间:2016-06-09 20:51:18

标签: 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) {
        // ...
    }

}

并且只能提供如下某些参数:

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

我正在尝试提供灵活且类型安全的API,这就是问题所在。给定的代码不灵活,因为我必须传入(并且知道API的用户)x的默认值才能调用该方法。以下是我想要的解决方案的一些挑战:

  • 类型安全是必须的 - 没有void bar(Map)签名,除非密钥可以某种方式被设置为类型安全。我意识到这一点,我可以在方法体中进行类型检查,但我正在努力避免这种冗余程度,因为我有许多这种“类型”的方法要写。
  • 我可以为每个方法签名使用一个类 - 如:

    class BarArgs {
        String x = SomeOtherClass.DEFAULT_X
        String y
        String z
    }
    

    并将其定义为:

    void bar(BarArgs barArgs) {
        // ...
    }
    

    使用地图构造函数f.bar(z: true)以我想要的方式调用它,但我的问题在于y上对象的默认值。没有办法处理它(我知道),而不必在调用方法时指定它,如:f.bar(y: f.y, z: true)。这对我的小样本很好,但是我在某些方法上看20-30个可选参数。

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

1 个答案:

答案 0 :(得分:6)

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

  1. 该类应具有一组默认属性。
  2. 每个方法都应该有一组默认参数。
  3. 方法默认值覆盖类默认值。
  4. 每个方法都可以有其他参数,不存在于类中。
  5. 方法参数不应修改类实例。
  6. 需要检查提供的参数类型。
  7. 我不确定数字5,因为没有明确指定,但它 看起来那就是你想要的。

    据我所知,groovy中没有任何内置功能可以支持所有这些, 但有几种方法可以让它以“简单易用”的方式运作。

    想到的一种方法是创建专门的参数类,但是 仅使用maps作为方法中的参数。用简单的超级课程 或者特性来验证和设置属性,这是一个单线来获得 每种方法的实际参数。

    这是一个特点和一些可以作为起点的例子:

    @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
    

    最后,一个简单的测试脚本;注释中方法调用的输出

    25 27 29 25 27 29 25 27 29 25 27 29 25 27 28