這一段翻譯自Groovy的規(guī)格文件的 3.2 Owner, delegate and this
3.2. Owner, delegate and this
為了明白delegate的概念,我們必須首先解釋清楚在閉包中關(guān)鍵字this的意義。一個(gè)閉包通常定義了一下三個(gè)量:
- this 指的是閉包定義所在的類
- owner 指的是閉包定義所在的對象,這個(gè)對象可能是類也可能是另一個(gè)閉包。【這是因?yàn)殚]包是可以嵌套定義的。】
- delegate 指的是一個(gè)第三方的(委托)對象,當(dāng)在閉包中調(diào)用的方法或?qū)傩詻]有定義時(shí),就會(huì)去委托對象上尋找。
3.2.1. this的意義
在閉包中,調(diào)用getThisObject返回這個(gè)閉包定義所在的對象。它等價(jià)于使用一個(gè)顯式的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中定義一個(gè)閉包,并返回getThisObject - 調(diào)用閉包將返回Enclosing類的一個(gè)實(shí)例。
- 通常你會(huì)使用這種便捷的語法
- 并且它將會(huì)返回同一個(gè)對象
- 如果閉包定義在一個(gè)內(nèi)部類中
- 則閉包中的
this將返回這個(gè)內(nèi)部類,而不是頂級類 - 在閉包嵌套的情況中,如此處的
c1閉包定義在nestedClosures閉包中 - 那么
this指的就是離c1最近的外部類,而不是外閉包?。?/strong>,
當(dāng)然有可能以以下的方式從包裹類中調(diào)用方法:
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上調(diào)用了toString方法,即外層包裹類的toString方法,它返回了Person類實(shí)例的一個(gè)描述。
3.2.2. 閉包的Owner
閉包的owner屬性和閉包的this非常相似,但是有一點(diǎn)微妙的差別:它返回這個(gè)閉包的直接包裹對象,可能是外層的嵌套閉包,也可能是一個(gè)類。
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 - 調(diào)用該閉包將返回
Enclosing的實(shí)例 - 通常會(huì)使用這種簡潔的語法
- 返回同一個(gè)對象
- 如果閉包是在一個(gè)內(nèi)部類中定義的
- 則
owner指定是內(nèi)部類,而不是頂級類 - 但是在嵌套閉包的情況下,如此處的
c1閉包定義在nestedClosures閉包內(nèi) - 則
owner指的是外層嵌套的閉包,這一點(diǎn)與this不同??!
3.2.3. 閉包的delegate
閉包中的delegate可以通過delegate屬性或者調(diào)用getDelegate方法來訪問。它是Groovy中幫助建立領(lǐng)域特定語言(domain specific languages)的強(qiáng)大的工具。閉包中的this和owner指的是閉包中的詞法作用域,而delegate則是一個(gè)可由用戶定義的對象。默認(rèn)情況下,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
}
}
- 通過調(diào)用
getDelegate()得到閉包的delegate對象 - 或使用
delegate屬性 - 二者都返回同一個(gè)對象
- 都是外圍的包裹類或閉包
- 尤其是在嵌套閉包的情況
- 此時(shí)
delegate就是owner
閉包的delegate屬性可以被修改成任何對象。讓我們通過創(chuàng)建兩個(gè)類來說明這一點(diǎn),這兩個(gè)類都不是互相的子類但是它們都定義了一個(gè)名為name的屬性:
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
然后我們定義一個(gè)閉包,它從delegate上取name屬性并返回:
def upperCasedName = { delegate.name.toUpperCase() }
通過改變delegate,我們可以看到目標(biāo)對象將被改變:
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
此時(shí)。這種效果和使用一個(gè)定義在閉包的詞法作用域內(nèi)的target對象沒有差別:
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
但是本質(zhì)上卻有重要區(qū)別:
-
target是一個(gè)局部變量的引用 -
delegate使用時(shí)可以不帶有前綴
3.2.4. 委托策略
閉包內(nèi)任何時(shí)候,只要使用一個(gè)沒有被引用的屬性,就會(huì)委托給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沒有被閉包所在的詞法作用域內(nèi)任何一個(gè)變量引用 - 改變
delegate,使之指向一個(gè)Person類實(shí)例 - 方法調(diào)用成功
這段代碼能夠工作的原因是因?yàn)?code>name屬性將會(huì)被委托給delegate對象執(zhí)行!這是在閉包內(nèi)解析屬性或方法調(diào)用的一種很有效的方法,從而沒有必要設(shè)置一個(gè)顯式的delegate.receiver:根據(jù)默認(rèn)的閉包委托策略,這種行為是可以的。一個(gè)閉包定義了多種可供選擇的解析策略:
-
Closure.OWNER_FIRST是默認(rèn)策略。如果一個(gè)屬性/方法存在于owner上,那么就會(huì)在owner上調(diào)用;否則,就會(huì)委托給delegate。 -
Closure.DELEGATE_FIRST和上面策略剛好相反:delegate首先被委托,其次再委托給owner。 -
Closure.OWNER_ONLY即只在owner上解析屬性/方法,delegate被忽略。 -
Closure.DELEGATE_ONLY即只在delegate上解析屬性/方法,owner被忽略。 -
Closure.TO_SELF可以被那些需要高級的元編程技術(shù)并卻希望實(shí)現(xiàn)自定義的解析策略的開發(fā)者使用:解析并不是在delegate或者owner上進(jìn)行,而是只在閉包類本身上進(jìn)行。只有實(shí)現(xiàn)了自己的Closure子類,這么使用才是被允許的。
讓我們用代碼解釋這個(gè)owner first的默認(rèn)策略:
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
- 定義一個(gè)閉包成員變量,它引用了
name屬性 -
Thing類和Person類都定義了name屬性 - 使用默認(rèn)策略,
name屬性會(huì)在owner上解析(斷言為真) - 改變
delegate使之指向Thing的實(shí)例 - 并沒有什么被改變,依然在
owner上解析(斷言為真)
然而我們可以改變這種默認(rèn)策略:
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
通過改變resolveStrategy,我們可以修改Groovy解析“隱式this”引用的方式:在這種情況下,name會(huì)首先在delegate上查詢,如果沒找到,則到owner上。因?yàn)?code>name在delegate(引用Thing類的實(shí)例)上有定義,所以被使用。
"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
}
在這個(gè)例子中,我們定義了兩個(gè)類,它們都含有name屬性但是只有Person類聲明了age屬性。Person類同時(shí)也聲明了一個(gè)閉包,其中引用了age屬性。我們可以將默認(rèn)解析策略從"owner first"改變到"delegate only"。因?yàn)檫@個(gè)閉包的owner是Person類,所以我們可以檢查delegate是不是Person類的一個(gè)實(shí)例,如果是,那么就能成功調(diào)用這個(gè)閉包,但是我們將它的delegate改成了Thing類的實(shí)例,所以運(yùn)行時(shí)會(huì)報(bào)groovy.lang.MissingPropertyException異常。盡管這個(gè)閉包被定義在Person類中,但是owner并沒有被使用。
關(guān)于如何利用這一特性來開發(fā)DSL(領(lǐng)域特定語言)的詳細(xì)說明可以參照:dedicated section of the manual.