Статьи

Функции высшего порядка с Groovy, часть 3

В первой и второй частях я говорил о том, как замыкания работают в Groovy, как их можно каррировать, как методы Java можно преобразовывать в замыкания и как замыкания можно преобразовывать в методы Java.

В этой последней части я расскажу о двух других аспектах замыканий: их механизм делегирования и синтаксис сборщика Groovy.

Рассмотрим этот скрипт:

def log(String message) {
    println message
}

def cl = {
    log("Hello, World!")
}

cl()

Какой метод log () вызывается в строке 6? Интуитивно понятно, что это должен быть метод log (), объявленный в строках 1-3. И для этого сценария это правильный ответ. Но это не должно быть так.

Вызовы методов и свойств из замыканий делегируются. По умолчанию — как в примере выше — вызовы делегируются владельцу закрытия. Владелец — свойство только для чтения, которое назначается при создании замыкания.

Распечатаем все методы класса объекта owner:

def log(String message) {
    println message
}

def cl = {
    owner.getClass().declaredMethods.each {
        log("${it}")
    }
}

cl()

Метод log () занимает третье место в списке ( delegate_test.groovyэто имя моего файла скрипта):

public static transient void delegate_test.main(java.lang.String[])
public java.lang.Object delegate_test.run()
public java.lang.Object delegate_test.log(java.lang.String)
[...]

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

Рассмотрим этот скрипт:

def log(String message) {
    println message
}

def cl = {
    log("Hello, World!")
}

assert cl.delegate == cl.owner

cl.delegate = null
cl.resolveStrategy = Closure.DELEGATE_ONLY

cl()

В строке 6 замыкание вызывает log()метод. В строке 11 delegateсвойство замыкания (которое объявлено в groovy.lang.Closureклассе) установлено в null. В строке 12 resolveStrategyсвойство установлено на DELEGATE_ONLY. Когда я запускаю этот скрипт, я получаю сообщение об ошибке:

Caught: groovy.lang.MissingMethodException: No signature of method: delegate_test$_run_closure1.log() 
            is applicable for argument types: (java.lang.String) values: {"Hello, World!"}
at delegate_test$_run_closure1.doCall(delegate_test.groovy:6)
at delegate_test$_run_closure1.doCall(delegate_test.groovy)
at delegate_test.run(delegate_test.groovy:14)
at delegate_test.main(delegate_test.groovy)

Поскольку для закрытия не задан объект делегата, а стратегия разрешения для делегирования вызовов установлена DELEGATE_ONLYна делегирование вызова, происходит сбой с помощью MissingMethodException.

Но почему вы хотите установить объект делегата на замыкание? Сборщики в Groovy делают это, чтобы контролировать, какие методы выполняются из замыканий. Это пример синтаксиса компоновщика для передачи замыканий в методы:

html {
    head {

    }
    body {

    }
}

Эти три метода, которые называются — html(), head()и body()— получите Closureобъект как последнее значение аргумента. Вот пример того, как html()метод может быть объявлен:

def html(Closure cl) {

}

Когда методы получают Closureобъект, они должны вызвать его, иначе он не будет выполнен. Например, пустое тело html()метода указанного выше метода не вызывает закрытие, которое оно получает, поэтому методы head()and body()и никогда не будут вызваны.

Чтобы исправить это, должен быть вызван объект замыкания. Это может быть так просто:

def html(Closure cl) {
    cl()
}

Однако, для того чтобы это работало head()и body()методы должны быть объявлены в классе объекта владельца:

def html(Closure cl) {
    cl()
}

def head() {

}

def body() {

}

Возможно, это не то, что вам нужно, поскольку теперь пользователи могут вызывать метод head()или, body()не вызывая html()метод в первую очередь. Решение состоит в том, чтобы переместить head()и body()методы другого класса, а также установить объект делегата для закрытия:

def html(Closure cl) {
    cl.delegate = new HtmlDelegate()
    cl.resolveStrategy = Closure.DELEGATE_FIRST
    cl()
}

class HtmlDelegate {
    def head() {

    }

    def body() {

    }
}

Устанавливая объект делегата в строке 2 и resolveStrategyсвойство для DELEGATE_FIRSTметодов, свойства и переменную все еще можно вызывать для объекта-владельца. Однако объект делегата получает приоритет.

При выставлении delegateи resolveStrategyсвойства изменить состояние Closureобъекта , и это может вызвать проблемы безопасности потоков. Обычно это не относится к сборщикам Groovy, но может быть и в других сценариях.

Чтобы устранить эту проблему, вызовите clone()метод для Closureобъекта:

def html(Closure cl) {
    def copy = cl.clone()
    copy.delegate = new HtmlDelegate()
    copy.resolveStrategy = Closure.DELEGATE_FIRST
    copy()
}

Вот и все для функций высшего порядка с Groovy. Есть еще несколько вещей, которые вы можете рассказать, но благодаря тому, что вы узнали из этой серии, вы сможете пройти долгий путь.

Функции высшего порядка предлагают альтернативную парадигму классическим Java-методам, к которым мы так привыкли. Это другой способ построения программ, и часто он более эффективен. Синтаксис компоновщика — это только один пример работы функций более высокого порядка. Вы можете применить любой из этих методов самостоятельно.

Преобразование методов Java в функции высшего порядка и обратно — вот что делает их реализацию в Groovy такой интересной для меня. Если ничего другого, это дает мне альтернативный способ игры с существующим кодом Java, который не доступен без Groovy.

Удачного кодирования!