这一段翻译自Groovy的规格文件的 3.2 Owner, delegate and this
3.2. Owner, delegate and this
为了明白delegate的概念,我们必须首先解释清楚在闭包中关键字this的意义。一个闭包通常定义了一下三个量:
- this 指的是闭包定义所在的类
- owner 指的是闭包定义所在的对象,这个对象可能是类也可能是另一个闭包。【这是因为闭包是可以嵌套定义的。】
- delegate 指的是一个第三方的(委托)对象,当在闭包中调用的方法或属性没有定义时,就会去委托对象上寻找。
3.2.1. this的意义
在闭包中,调用getThisObject
返回这个闭包定义所在的对象。它等价于使用一个显式的this
:
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() } //#1
assert whatIsThisObject() == this //#2
def whatIsThis = { this } //#3
assert whatIsThis() == this //#4
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { this } //#5
}
void run() {
def inner = new Inner()
assert inner.cl() == inner //#6
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { this } //#7
cl()
}
assert nestedClosures() == this //#8
}
}
- 在
Enclosing
中定义一个闭包,并返回getThisObject
- 调用闭包将返回Enclosing类的一个实例。
- 通常你会使用这种便捷的语法
- 并且它将会返回同一个对象
- 如果闭包定义在一个内部类中
- 则闭包中的
this
将返回这个内部类,而不是顶级类 - 在闭包嵌套的情况中,如此处的
c1
闭包定义在nestedClosures
闭包中 - 那么
this
指的就是离c1
最近的外部类,而不是外闭包!!,
当然有可能以以下的方式从包裹类中调用方法:
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
String msg = this.toString() //#1
println msg
msg
}
cl()
}
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
该闭包在this
上调用了toString
方法,即外层包裹类的toString
方法,它返回了Person类实例的一个描述。
3.2.2. 闭包的Owner
闭包的owner
属性和闭包的this
非常相似,但是有一点微妙的差别:它返回这个闭包的直接包裹对象,可能是外层的嵌套闭包,也可能是一个类。
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() } //#1
assert whatIsOwnerMethod() == this //#2
def whatIsOwner = { owner } //#3
assert whatIsOwner() == this ///#4
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner } //#5
}
void run() {
def inner = new Inner()
assert inner.cl() == inner //#6
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner } //#7
cl()
}
assert nestedClosures() == nestedClosures //#8
}
}
- 定义在
Enclosing
类中的闭包,返回getOwner
- 调用该闭包将返回
Enclosing
的实例 - 通常会使用这种简洁的语法
- 返回同一个对象
- 如果闭包是在一个内部类中定义的
- 则
owner
指定是内部类,而不是顶级类 - 但是在嵌套闭包的情况下,如此处的
c1
闭包定义在nestedClosures
闭包内 - 则
owner
指的是外层嵌套的闭包,这一点与this
不同!!
3.2.3. 闭包的delegate
闭包中的delegate
可以通过delegate
属性或者调用getDelegate
方法来访问。它是Groovy中帮助建立领域特定语言(domain specific languages)的强大的工具。闭包中的this
和owner
指的是闭包中的词法作用域,而delegate
则是一个可由用户定义的对象。默认情况下,delegate
等于owner
:
class Enclosing {
void run() {
def cl = { getDelegate() } //#1
def cl2 = { delegate } //#2
assert cl() == cl2() //#3
assert cl() == this //#4
def enclosed = {
{ -> delegate }.call() //#5
}
assert enclosed() == enclosed //#6
}
}
- 通过调用
getDelegate()
得到闭包的delegate
对象 - 或使用
delegate
属性 - 二者都返回同一个对象
- 都是外围的包裹类或闭包
- 尤其是在嵌套闭包的情况
- 此时
delegate
就是owner
闭包的delegate
属性可以被修改成任何对象。让我们通过创建两个类来说明这一点,这两个类都不是互相的子类但是它们都定义了一个名为name
的属性:
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
然后我们定义一个闭包,它从delegate
上取name
属性并返回:
def upperCasedName = { delegate.name.toUpperCase() }
通过改变delegate
,我们可以看到目标对象将被改变:
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
此时。这种效果和使用一个定义在闭包的词法作用域内的target
对象没有差别:
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
但是本质上却有重要区别:
-
target
是一个局部变量的引用 -
delegate
使用时可以不带有前缀
3.2.4. 委托策略
闭包内任何时候,只要使用一个没有被引用的属性,就会委托给delegate对象
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }//#1
cl.delegate = p //#2
assert cl() == 'IGOR' //#3
-
name
没有被闭包所在的词法作用域内任何一个变量引用 - 改变
delegate
,使之指向一个Person
类实例 - 方法调用成功
这段代码能够工作的原因是因为name
属性将会被委托给delegate
对象执行!这是在闭包内解析属性或方法调用的一种很有效的方法,从而没有必要设置一个显式的delegate.receiver
:根据默认的闭包委托策略,这种行为是可以的。一个闭包定义了多种可供选择的解析策略:
-
Closure.OWNER_FIRST
是默认策略。如果一个属性/方法存在于owner
上,那么就会在owner
上调用;否则,就会委托给delegate
。 -
Closure.DELEGATE_FIRST
和上面策略刚好相反:delegate
首先被委托,其次再委托给owner
。 -
Closure.OWNER_ONLY
即只在owner
上解析属性/方法,delegate
被忽略。 -
Closure.DELEGATE_ONLY
即只在delegate
上解析属性/方法,owner
被忽略。 -
Closure.TO_SELF
可以被那些需要高级的元编程技术并却希望实现自定义的解析策略的开发者使用:解析并不是在delegate
或者owner
上进行,而是只在闭包类本身上进行。只有实现了自己的Closure
子类,这么使用才是被允许的。
让我们用代码解释这个owner first
的默认策略:
class Person {
String name
def pretty = { "My name is $name" } //#1
String toString() {
pretty()
}
}
class Thing {
String name //#2
}
def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')
assert p.toString() == 'My name is Sarah' //#3
p.pretty.delegate = t //#4
assert p.toString() == 'My name is Sarah' //#5
- 定义一个闭包成员变量,它引用了
name
属性 -
Thing
类和Person
类都定义了name属性 - 使用默认策略,
name
属性会在owner
上解析(断言为真) - 改变
delegate
使之指向Thing
的实例 - 并没有什么被改变,依然在
owner
上解析(断言为真)
然而我们可以改变这种默认策略:
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
通过改变resolveStrategy
,我们可以修改Groovy解析“隐式this
”引用的方式:在这种情况下,name
会首先在delegate
上查询,如果没找到,则到owner
上。因为name
在delegate
(引用Thing
类的实例)上有定义,所以被使用。
"delegate first"和 "delegate only"(或"owner first"和"owner only") 之间的不同是,如果delegate
(或owner
)上并没有这种方法或者属性,例如:
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == 42
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42
cl.delegate = t
try {
cl()
assert false
} catch (MissingPropertyException ex) {
// "age" is not defined on the delegate
}
在这个例子中,我们定义了两个类,它们都含有name
属性但是只有Person
类声明了age
属性。Person
类同时也声明了一个闭包,其中引用了age
属性。我们可以将默认解析策略从"owner first"改变到"delegate only"。因为这个闭包的owner
是Person
类,所以我们可以检查delegate
是不是Person
类的一个实例,如果是,那么就能成功调用这个闭包,但是我们将它的delegate
改成了Thing
类的实例,所以运行时会报groovy.lang.MissingPropertyException
异常。尽管这个闭包被定义在Person类中,但是owner并没有被使用。
关于如何利用这一特性来开发DSL(领域特定语言)的详细说明可以参照:dedicated section of the manual.