Статьи

Groovy Goodness: добавление дополнительных методов с использованием модулей расширения

Groovy 2.0 принес нам модули расширения. Модуль расширения представляет собой файл JAR с классами, которые предоставляют дополнительные методы для существующих других классов, таких как JDK или сторонние библиотеки. Groovy использует этот механизм для добавления, например, дополнительных методов в Fileкласс. Мы можем реализовать наш собственный модуль расширения для добавления новых методов расширения в существующие классы. После того, как мы написали модуль, мы можем добавить его в путь к классам нашего кода или приложения, и все новые методы будут сразу же доступны.

Мы определяем новые методы расширения в вспомогательных классах, которые являются частью модуля. Мы можем создавать экземпляры и статические методы расширения, но нам нужен отдельный вспомогательный класс для каждого типа метода расширения. Мы не можем смешивать статические и методы расширения экземпляра в одном вспомогательном классе. Сначала мы создаем очень простой класс с методом расширения для Stringкласса. Первый аргумент метода расширения определяет тип или класс, к которому мы хотим добавить метод. Следующий код показывает метод likeAPirate. Метод расширения должен быть, publicи staticхотя мы создаем метод расширения экземпляра.

// File: src/main/groovy/com/mrhaki/groovy/PirateExtension.groovy
package com.mrhaki.groovy

class PirateExtension {
    static String likeAPirate(final String self) {
        // List of pirate language translations.
        def translations = [
            ["hello", "ahoy"], ["Hi", "Yo-ho-ho"],
            ['are', 'be'], ['am', 'be'], ['is', 'be'],
            ['the', "th'"], ['you', 'ye'], ['your', 'yer'],
            ['of', "o'"]
        ]
        
        // Translate the original String to a 
        // pirate language String.
        String result = self
        translations.each { translate ->
                result = result.replaceAll(translate[0], translate[1])
        }
        result
    }
}

Далее нам нужно создать файл дескриптора модуля расширения. В этом файле мы определяем имя вспомогательного класса, поэтому Groovy будет знать, как его использовать. Файл дескриптора должен быть размещен в META-INF/servicesкаталоге нашего архива модуля или пути к классам. Имя файла есть org.codehaus.groovy.runtime.ExtensionModule. В файле мы определяем имя нашего модуля, версию и имя вспомогательного класса. Имя вспомогательного класса определяется с помощью свойства extensionClasses:

# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = pirate-module
moduleVersion = 1.0
extensionClasses = com.mrhaki.groovy.PirateExtension

Теперь модуль расширения готов. Самый простой способ распространения модуля — это упаковать файл кода и дескриптора в файл JAR и поместить его в менеджер репозитория артефактов. Затем другие разработчики могут использовать инструменты сборки, такие как Gradle или Maven, чтобы включить модуль расширения в свои проекты и приложения. Если мы используем Gradle для создания JAR-файла, нам нужен только этот небольшой скрипт сборки:

/ File: build.gradle
apply plugin: 'groovy'

repositories.mavenCentral()

dependencies {
    // Since Gradle 1.4 we don't use the groovy configuration
    // to define dependencies. We can simply use the
    // compile and testCompile configurations.
    compile 'org.codehaus.groovy:groovy-all:2.0.6'
}

Теперь мы можем вызвать $ gradle buildи получили модуль расширения.

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

// File: src/test/groovy/com/mrhaki/groovy/PirateExtensionSpec.groovy
package com.mrhaki.groovy

import spock.lang.Specification

class PirateExtensionSpec extends Specification {

    def "likeAPirate method should work as instance method on a String value"() {
        given:
        final String originalText = "Hi, Groovy is the greatest language of the JVM."

        expect:
        originalText.likeAPirate() == "Yo-ho-ho, Groovy be th' greatest language o' th' JVM."
    }

}

Мы добавляем зависимость к Споку в нашем файле сборки Gradle:

/ File: build.gradle
apply plugin: 'groovy'

repositories.mavenCentral()

dependencies {
    // Since Gradle 1.4 we don't use the groovy configuration
    // to define dependencies. We can simply use the
    // compile and testCompile configurations.
    compile 'org.codehaus.groovy:groovy-all:2.0.6'

    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
}

Мы можем запустить $ gradle testдля запуска спецификации Spock и протестировать наш новый метод расширения.

Чтобы добавить статический метод в существующий класс, нам нужно добавить дополнительный вспомогательный класс в наш модуль расширения и дополнительное свойство в наш файл дескриптора для регистрации вспомогательного класса. Первый аргумент метода расширения определяет тип, к которому мы хотим добавить статический метод. В следующем вспомогательном классе мы добавляем talkLikeAPirate()в Stringкласс метод расширения .

/ File: src/main/groovy/com/mrhaki/groovy/PirateStaticExtension.groovy
package com.mrhaki.groovy

class PirateStaticExtension {
    static String talkLikeAPirate(final String type) {
        "Arr, me hearty,"
    }

Мы изменим файл дескриптора и добавим staticExtensionClassesсвойство:

# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = pirate-module
moduleVersion = 1.0
extensionClasses = com.mrhaki.groovy.PirateExtension
staticExtensionClasses = com.mrhaki.groovy.PirateStaticExtension

В нашей спецификации Spock мы добавляем дополнительный тест для нашего нового статического метода talkLikeAPirate()в Stringклассе:

// File: src/test/groovy/com/mrhaki/groovy/PirateExtensionSpec.groovy
package com.mrhaki.groovy

import spock.lang.Specification

class PirateExtensionSpec extends Specification {

    def "likeAPirate method should work as instance method on a String value"() {
        given:
        final String originalText = "Hi, Groovy is the greatest language of the JVM."

        expect:
        originalText.likeAPirate() == "Yo-ho-ho, Groovy be th' greatest language o' th' JVM."
    }

    def "talkLikeAPirate method should work as static method on String class"() {
        expect:
        "Arr, me hearty, Groovy rocks!" == String.talkLikeAPirate() + " Groovy rocks!"
    }

}

Написано с Groovy 2.1