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.

推荐阅读更多精彩内容

  • 努力的人,应该像好色那样好学 做Android开发的同学,对Gradle肯定不陌生,我们用它配置、构建工程,可能还...
    HitenDev阅读 9,805评论 7 43
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 119,422评论 16 133
  • 本文介绍了Groovy闭包的有关内容。闭包可以说是Groovy中最重要的功能了。如果没有闭包,那么Groovy除了...
    乐百川阅读 5,910评论 3 11
  • 上上周日经历了彻底的绝望,痛彻心扉。难过到上周一的晚上,突然灵光乍现,仿佛一下看到了“真相”,然后所有的情绪瞬间平...
    就让自己在这里阅读 73评论 0 0
  • 夜伴莺啼,枕着所有活跃的思绪,寻找曾经。 曾经是一部电影,曾经是一首歌曲。曾经的曾经,被尘埃封匿在遗忘...
    飓风公益阅读 57评论 0 0