Статьи

Groovy Closures: это, владелец, делегат, давайте сделаем DSL

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

В отношении вышеуказанного кода:

  • Значение 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().outerClosure
closure()
 
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().closure
closure()

В этом случае 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().closure
closure.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().closure
closure.delegate = new MyOtherClass() 
closure()     // outputs: I am over in here in myOtherClass
 
closure = new MyClass().closure
closure.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 delegate
 
constraints.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.