Groovy закрытия супер круто. Чтобы полностью понять их, я думаю, что действительно важно понять значение этого , владельца и делегата . В общем:
- this : относится к экземпляру класса, в котором было определено замыкание.
- owner : то же самое, что и это , если только замыкание не было определено внутри другого замыкания, в этом случае владелец ссылается на внешнее замыкание.
- делегат : тот же, что и владелец. Но это единственное, что может быть программно изменено, и это то, что делает замыкания Groovy действительно мощными.
Смущенный? Давайте посмотрим на некоторый код.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class MyClass { def outerClosure = { println this.class.name // outputs MyClass println owner.class.name // outputs MyClass println delegate.class.name //outputs MyClass def nestedClosure = { println this.class.name // outputs MyClass println owner.class.name // outputs MyClass$_closure1 println delegate.class.name // outputs MyClass$_closure1 } nestedClosure() }}def closure = new MyClass().closureclosure() |
В отношении вышеуказанного кода:
- Значение this всегда относится к экземпляру включающего класса.
- владелец всегда такой же, как этот , за исключением вложенных замыканий.
- По умолчанию делегат совпадает с владельцем. Это можно изменить, и мы увидим это через секунду.
Так какой в этом смысл, владелец , делегат ? Хорошо помните, что замыкания — это не просто анонимные функции. Если бы они были, мы могли бы просто назвать их Лямбдами, и нам не пришлось бы придумывать другое слово, не так ли?
Замыкания выходят за пределы лямбды — они связывают или «закрывают» переменные, которые явно не определены в области замыкания. Опять же, давайте посмотрим на некоторый код.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class MyClass { String myString = "myString1" def outerClosure = { println myString; // outputs myString1 def nestedClosure = { println myString; // outputs myString1 } nestedClosure() }}MyClass myClass = new MyClass()def closure = new MyClass().outerClosureclosure()println myClass.myString |
Итак, и замыкание, и nestedClosure имеют доступ к переменным в экземпляре класса, в котором они были определены. Это очевидно. Но как именно они разрешают ссылку myString ? Ну, это так. Если переменная не была явно определена в замыкании, то проверяется область действия this , затем область действия владельца и область действия делегата . В этом примере myString не определено ни в одном из замыканий, поэтому groovy проверяет их ссылки this и видит, что myString определена там, и использует это. Хорошо, давайте посмотрим на пример, где он не может найти переменную в замыкании и не может найти ее в области видимости замыкания, но он может найти ее в области видимости владельца замыкания.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
class MyClass { def outerClosure = { def myString = "outerClosure"; def nestedClosure = { println myString; // outputs outerClosure } nestedClosure() }}MyClass myClass = new MyClass()def closure = new MyClass().closureclosure() |
В этом случае Groovy не может найти myString в nestedClosure или в этой области видимости. Затем он проверяет область владельца , которой для nestedClosure является externalClosure . Он находит myString там и использует это. Теперь давайте рассмотрим пример, в котором Groovy не может найти переменную в замыкании или в этой области или в области владельца, но может найти ее в области делегирования замыкания. Как обсуждалось ранее, область действия делегата владельца совпадает с областью действия владельца, если она явно не изменена. Итак, чтобы сделать это немного интереснее, давайте изменим делегата.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class MyOtherClass { String myString = "I am over in here in myOtherClass"}class MyClass { def closure = { println myString }}MyClass myClass = new MyClass()def closure = new MyClass().closureclosure.delegate = new MyOtherClass()closure() // outputs: "I am over in here in myOtherClass" |
Возможность иметь так много контроля над лексической областью замыканий в Groovy дает огромную силу. Даже если делегат установлен, его можно изменить на что-то другое, это означает, что мы можем сделать поведение замыкания супер динамическим.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class MyOtherClass { String myString = "I am over in here in myOtherClass"}class MyOtherClass2 { String myString = "I am over in here in myOtherClass2"}class MyClass { def closure = { println myString }}MyClass myClass = new MyClass()def closure = new MyClass().closureclosure.delegate = new MyOtherClass() closure() // outputs: I am over in here in myOtherClassclosure = new MyClass().closureclosure.delegate = new MyOtherClass2() closure() // outputs: I am over in here in myOtherClass2 |
Итак, теперь должно быть немного яснее, что на самом деле соответствуют этому владельцу и делегату . Как уже говорилось, сначала проверяется само замыкание, за которым следует область действия замыкания, затем владелец замыкания, а затем его делегат . Однако Groovy настолько гибок, что эту стратегию можно изменить. Каждое замыкание имеет свойство под названием resolvedStrategy . Это может быть установлено в:
- Closure.OWNER_FIRST
- Closure.DELEGATE_FIRST
- Closure.OWNER_ONLY
- Closure.DELEGATE_ONLY
Итак, где хороший пример практического использования динамической настройки свойства делегата. Ну, вы видите в ГОРМ для Грааля. Предположим, у нас есть следующий класс домена:
|
1
2
3
4
5
6
7
|
class Author { String name static constraints = { name size: 10..15 }} |
В классе Author мы можем видеть ограничение, определенное с использованием того, что выглядит как DSL, тогда как в мире Java / Hibernate мы не сможем написать выразительный DSL и вместо этого использовать аннотацию (которая лучше, чем XML, но все же не так аккуратна, как DSL). Так почему же мы можем использовать DSL в Groovy? Что ж, это из-за возможностей делегирования настроек в замыканиях, которые добавляются в набор инструментов метапрограммирования Groovy. В объекте Author GORM ограничение является замыканием, которое вызывает метод имени с одним параметром размера имени, который имеет значение в диапазоне от 10 до 15. Он также может быть записан с меньшим значением DSL’y как:
|
1
2
3
4
5
6
7
|
class Author { String name static constraints = { name(size: 10..15) }} |
В любом случае, за кулисами Grails ищет закрытие ограничений и назначает свой делегат специальному объекту, который синтезирует логику ограничений. В псевдокоде это будет примерно так …
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
// Set the constraints delegateconstraints.delegate = new ConstraintsBuilder(); // delegate is assigned before the closure is executed.class ConstraintsBuilder = { // // ... // In every Groovy object methodMissing() is invoked when a method that does not exist on the object is invoked // In this case, there is no name() method so methodMissing will be invoked. // ... def methodMissing(String methodName, args) { // We can get the name variable here from the method name // We can get that size is 10..15 from the args ... // Go and do stuff with hibernate to enforce constraints }} |
Так что у вас есть это. Замыкания очень мощные, они могут делегировать объекты, которые могут быть установлены динамически во время выполнения. Это играет важную роль в возможностях метапрограммирования Groovy, что означает, что Groovy может иметь несколько очень выразительных DSL.
| Ссылка: | Groovy Closures: владелец, делегат, давайте сделаем DSL от нашего партнера по JCG Алекса Стейвли в блоге Tech Blog в Дублине . |