Groovy闭包:this、owner、delegate(规格文件节选翻译)

这一段翻译自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     
    }
}
  1. Enclosing中定义一个闭包,并返回getThisObject
  2. 调用闭包将返回Enclosing类的一个实例。
  3. 通常你会使用这种便捷的语法
  4. 并且它将会返回同一个对象
  5. 如果闭包定义在一个内部类中
  6. 则闭包中的this将返回这个内部类,而不是顶级类
  7. 在闭包嵌套的情况中,如此处的c1闭包定义在nestedClosures闭包中
  8. 那么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
    }
}
  1. 定义在Enclosing类中的闭包,返回getOwner
  2. 调用该闭包将返回Enclosing的实例
  3. 通常会使用这种简洁的语法
  4. 返回同一个对象
  5. 如果闭包是在一个内部类中定义的
  6. owner指定是内部类,而不是顶级类
  7. 但是在嵌套闭包的情况下,如此处的c1闭包定义在nestedClosures闭包内
  8. owner指的是外层嵌套的闭包,这一点与this不同!!

3.2.3. 闭包的delegate

闭包中的delegate可以通过delegate属性或者调用getDelegate方法来访问。它是Groovy中帮助建立领域特定语言(domain specific languages)的强大的工具。闭包中的thisowner指的是闭包中的词法作用域,而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    
    }
}
  1. 通过调用getDelegate()得到闭包的delegate对象
  2. 或使用delegate属性
  3. 二者都返回同一个对象
  4. 都是外围的包裹类或闭包
  5. 尤其是在嵌套闭包的情况
  6. 此时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  
  1. name没有被闭包所在的词法作用域内任何一个变量引用
  2. 改变delegate,使之指向一个Person类实例
  3. 方法调用成功

这段代码能够工作的原因是因为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
  1. 定义一个闭包成员变量,它引用了name属性
  2. Thing类和Person类都定义了name属性
  3. 使用默认策略,name属性会在owner上解析(断言为真)
  4. 改变delegate使之指向Thing的实例
  5. 并没有什么被改变,依然在owner上解析(断言为真)

然而我们可以改变这种默认策略:

p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

通过改变resolveStrategy,我们可以修改Groovy解析“隐式this”引用的方式:在这种情况下,name会首先在delegate上查询,如果没找到,则到owner上。因为namedelegate(引用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"。因为这个闭包的ownerPerson类,所以我们可以检查delegate是不是Person类的一个实例,如果是,那么就能成功调用这个闭包,但是我们将它的delegate改成了Thing类的实例,所以运行时会报groovy.lang.MissingPropertyException异常。尽管这个闭包被定义在Person类中,但是owner并没有被使用。

关于如何利用这一特性来开发DSL(领域特定语言)的详细说明可以参照:dedicated section of the manual.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 努力的人,应该像好色那样好学 做Android开发的同学,对Gradle肯定不陌生,我们用它配置、构建工程,可能还...
    HitenDev阅读 13,321评论 9 50
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,107评论 29 470
  • 本文介绍了Groovy闭包的有关内容。闭包可以说是Groovy中最重要的功能了。如果没有闭包,那么Groovy除了...
    乐百川阅读 7,499评论 3 13
  • 上上周日经历了彻底的绝望,痛彻心扉。难过到上周一的晚上,突然灵光乍现,仿佛一下看到了“真相”,然后所有的情绪瞬间平...
    就让自己在这里阅读 240评论 0 0