Создать новый класс "на лету"? - PullRequest
0 голосов
/ 09 июня 2018

... конкретно в Groovy (отсюда и тэг)?

В Java вы не можете этого сделать ... но в динамических языках (например, Python) вы обычно можете.

Anпопытка сделать что-то подобное в блоке given функции Спока (т. е. метод тестирования) встречается в Eclipse:

Groovy: определение класса здесь не ожидается.Пожалуйста, определите класс в соответствующем месте или, возможно, попробуйте использовать вместо него блок / закрытие.

... "подходящее" место, очевидно, будет вне функции.Это было бы неуклюже и не заводно.Пользуясь Groovy уже несколько месяцев, я чувствую, что Groovy должен предложить что-то более привлекательное.

Скажем, я хотел бы расширить свой abstract класс AbstractFoo и создать новый подкласс Foo, в моей функции, есть ли способ «использовать блок / замыкание» для достижения чего-токак это?

1 Ответ

0 голосов
/ 09 июня 2018

Вы можете просто создать анонимный класс, создав экземпляр AbstractFoo и предоставив встроенную реализацию абстрактных методов.Рассмотрим следующий пример:

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()

Оба foo1 и foo2 реализуют AbstractFoo, и они обеспечивают различную реализацию метода text(), что приводит к другому поведению метода bar().Запуск этого скрипта Groovy приводит к выводу на консоль следующих данных:

Hello, world!
Lorem ipsum dolor sit amet

Это не относится к Groovy, вы можете добиться точно такого же поведения с Java.Тем не менее, вы можете сделать его немного более «подходящим», приведя замыкание к классу AbstractFoo, примерно так:

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

В этом случае замыкание, которое возвращает «тест 123», обеспечивает реализацию дляабстрактный text() метод.Это работает так, если ваш абстрактный класс имеет только один абстрактный метод.

Абстрактный класс с несколькими абстрактными методами

Но что произойдет, если абстрактный класс имеет несколько абстрактных методов, которые мы хотим реализовать на лету?В этом случае мы можем обеспечить реализацию этих методов в виде карты, где ключи - это имена абстрактных методов, а значения - замыкания, обеспечивающие реализацию.Давайте рассмотрим следующий пример:

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()

В этом примере используется абстрактный класс с двумя абстрактными методами.Мы можем создать экземпляр этого класса, приведя карту типа Map<String, Closure<?>> к AbstractFoo class.Выполнение этого примера приводит к выводу на консоль следующих данных:

text: test 1, number: 23

Создание неанонимных классов на лету в Groovy

Groovy также позволяет создавать класс, например, из многострочной строки, используя 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, который расширяет AbstractFoo и предоставляет определение метода test().Этот подход довольно подвержен ошибкам, так как вы определяете новый класс как String, поэтому забудьте о любой поддержке IDE при отлове ошибок и предупреждений.

Предоставление подкласса в спецификации теста

Ваш первоначальный вопросупоминалось о попытке создать класс для спецификации в given: блоке Спока.Я настоятельно рекомендую использовать самый простой из доступных инструментов - создание вложенного частного статического класса, чтобы вы могли легко получить к нему доступ внутри теста и не показывать его вне теста.Примерно так:

class MySpec extends Specification {

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

        when:
        //....

        then:
        ///....
    }

    private static class Foo extends AbstractFoo  {

    }
}
...