创建一个新的 class "on the fly"?

Create a new class "on the fly"?

...特别是在 Groovy(因此标记)?

在 Java 中你不能这样做......但在动态语言中(例如 Python)你通常可以。

在 Eclipse 中,试图在 Spock 功能(即测试方法)的 given 块中做类似的事情:

Groovy:Class definition not expected here. Please define the class at an appropriate place or perhaps try using a block/Closure instead.

... "appropriate" 地方显然在功能之外。这会很笨重,而不是 groovy。使用 Groovy 几个月后,我觉得 Groovy 应该在什么时候提供更棒的东西。

假设我想扩展我的 abstract class AbstractFoo 并在我的功能中创建一个新的子class Foo,是有什么方法可以 "use a block/Closure" 实现这样的目标吗?

您可以通过实例化 AbstractFoo 并提供抽象方法的内联实现来简单地创建匿名 class。考虑以下示例:

abstract class AbstractFoo {
    void bar() {
        println text()
    }

    abstract String text()
}

def foo1 = new AbstractFoo() {
    @Override
    String text() {
        return "Hello, world!"
    }
}

def foo2 = new AbstractFoo() {
    @Override
    String text() {
        return "Lorem ipsum dolor sit amet"
    }
}

foo1.bar()
foo2.bar()

foo1foo2 都实现了 AbstractFoo,它们提供了不同的 text() 方法实现,导致不同的 bar() 方法行为。 运行 此 Groovy 脚本向控制台生成以下输出:

Hello, world!
Lorem ipsum dolor sit amet

这不是 Groovy 特有的,您可以使用 Java 实现完全相同的行为。但是,您可以通过将闭包转换为 AbstractFoo class 来使它 "groovier" 多一点,如下所示:

def foo3 = { "test 123" } as AbstractFoo
foo3.bar()

在这种情况下,闭包 returns "test 123" 提供抽象 text() 方法的实现。如果您的抽象 class 只有一个抽象方法,它就会像这样工作。

具有多个抽象方法的抽象 class

但是如果一个抽象 class 有多个我们想要动态实现的抽象方法会怎样?在这种情况下,我们可以将此方法的实现作为映射提供,其中键是抽象方法的名称,值是提供实现的闭包。让我们看看下面的例子:

abstract class AbstractFoo {
    abstract String text()
    abstract int number()
    void bar() {
        println "text: ${text()}, number: ${number()}"
    }
}

def foo = [
    text: { "test 1" },
    number: { 23 }
] as AbstractFoo

foo.bar()

这个例子使用了一个抽象 class 和两个抽象方法。我们可以通过将 Map<String, Closure<?>> 类型的映射转换为 AbstractFoo class 来实例化此 class。 运行 此示例向控制台生成以下输出:

text: test 1, number: 23

正在 Groovy

中即时创建非匿名 classes

Groovy 还允许您创建 class 例如来自使用 GroovyClassLoader.parseClass(input) 方法的多行字符串。让我们看看下面的例子:

abstract class AbstractFoo {
    void bar() {
        println text()
    }

    abstract String text()
}

def newClassDefinitionAsString = '''
class Foo extends AbstractFoo {
    String text() {
        return "test"
    }
}
'''

def clazz = new GroovyClassLoader(getClass().getClassLoader()).parseClass(newClassDefinitionAsString)

def foo = ((AbstractFoo) clazz.newInstance())

foo.bar()

这里我们定义了一个名为 Foo 的非匿名 class,它扩展了 AbstractFoo 并提供了 test() 方法的定义。这种方法很容易出错,因为您将新的 class 定义为字符串,所以忘记任何 IDE 捕获错误和警告的支持。

在测试规范中提供子class

您最初的问题提到了尝试在 given: Spock 块中为规范创建 class。我强烈建议使用最简单的可用工具——创建一个嵌套的私有静态 class,这样您就可以在测试中轻松访问它,而不会在测试之外公开它。像这样:

class MySpec extends Specification {

    def "should do something"() {
        given:
        Class<?> clazz = Foo.class

        when:
        //....

        then:
        ///....
    }

    private static class Foo extends AbstractFoo  {

    }
}