闭包是Groovy的一个非常重要的特性,可以说他是DSL的基础。闭包不是Groovy的首创,但是它支持这一重要特性,这就使用我们的代码灵活、轻量、可复用,再也不用像Java一样动不动就要搞一个类了,虽然Java后来有了匿名内部类,但是一样冗余不灵活。
初识闭包
前面我们讲过,闭包其实就是一段代码块,下面我们就一步步实现自己的闭包,了解闭包的it变量的由来。集合的each方法我们已经非常熟悉了,我们就以其为例,实现一个类似的闭包功能。
task helloClosure << { //使用我们自定义的闭包
customEach {
println it
}
}
def customEach(closure){ //模拟一个有10个元素的集合,开始迭代
for(int i in 1..10){
closure(i)
}
}
在上面的例子中我们定义了一个方法customEach,它只有一个参数,用于接收一个闭包(代码块),那么这个闭包如何执行呢?很简单,跟一对括号就是执行了,会JavaScript的朋友是不是觉得很熟悉,把它当做一个方法调用,括号里的参数就是该闭包接收的参数,如果只有一个参数,那么就是我们的it变量了。
向闭包传递参数
上一节我们讲了,当闭包有一个参数时,默认就是it;当有多个参数是,it就不能表示了,我们需要把参数一一列出。
task helloClosure << { //多个参数
eachMap {k,v ->
println "${k} is ${v}"
}
}
def eachMap(closure){
def map1 = ["name":"张三","age":18]
map1.each {
closure(it.key,it.value)
}
}
从例子中我们可以看到,我们为闭包传递了两个参数,一个key,一个value,便于我们演示。这是我们我们就不能使用it了,必须要显式的声明出来,如例子中的k,v,符号->用于把闭包的参数和主体区分开来。
闭包委托
Groovy闭包的强大之处在于它支持闭包方法的委托。Groovy的闭包有thisObject、owner、delegate三个属性,当你在闭包内调用方法时,由他们来确定使用哪个对象来处理。默认情况下delegate和owner是相等的,但是delegate是可以被修改的,这个功能是非常强大的,Gradle中的很闭包的很多功能都是通过修改delegate实现的。
task helloDelegate << {
new Delegate().test {
println "thisObject:${thisObject.getClass()}"
println "owner:${owner.getClass()}"
println "delegate:${delegate.getClass()}"
method1()
it.method1()
}
}
def method1(){
println "Context this:${this.getClass()} in root"
println "method1 in root"
}
class Delegate {
def method1(){
println "Delegate this:${this.getClass()} in Delegate"
println "method1 in Delegate"
}
def test(Closure<Delegate> closure){
closure(this)
}
}
运行我们可以看到输出:
thisObject:class build_e27c427w88bo0afju9niqltzf
owner:class build_e27c427w88bo0afju9niqltzf$_run_closure2
delegate:class build_e27c427w88bo0afju9niqltzf$_run_closure2
this:class build_e27c427w88bo0afju9niqltzf in root
method1 in root
this:class Delegate in Delegate
method1 in Delegate
通过上面的例子我们发现,thisObject的优先级最高,默认情况下,优先使用thisObject来处理闭包中调用的方法,如果有则执行。从输出中我们也可以看到这个thisObject其实就是这个构建脚本的上下文,他和脚本中的this对象是相等的。
从例子中也证明了delegate和owner是相等的,他们两个的优先级是owner要比delegate高,所以对于闭包内方法的处理顺序是thisObject>owner>delegate。
在DSL中,比如Gradle,我们一般会指定delegate为当前的it,这样我们在闭包内就可以对该it进行配置,或者调用其方法。
task configClosure << {
person {
personName = "张三"
personAge = 20
dumpPerson()
}
}
class Person {
String personName int personAge
def dumpPerson(){
println "name is ${personName},age is ${personAge}"
}
}
def person(Closure<Person> closure){
Person p = new Person();
closure.delegate = p //委托模式优先
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure(p)
}
例子中我们设置了委托对象为当前创建的Person实例,并且设置了委托模式优先,所以我们在试用person方法创建一个Person的实例时,可以在闭包里直接对该Person实例配置,有没有发现和我们在Gradle试用task创建一个Task的用法很像,其实在Gradle中有很多类似的用法,在Gradle也基本上都是使用delegate的方式使用闭包进行配置等操作。
DSL
DSL(Domain Specific Language),领域特定语言,说白了就是专门关注某一领域专门语言,在于专,而不是全,所以才叫领域特定的,而不是像Java这种通用全面的语言。
Gradle就是一门DSL,他是基于Groovy的,专门解决自动化构建的DSL。自动化构建太复杂、太麻烦、太专业,我们理解不了,没问题,专家们就开发了DSL—Gradle,我们作为开发者只要按照Gradle DSL定义的,书写相应的Gradle脚本就可以达到我们自动化构建的目的,这也是DSL的初衷。
DSL涉及的东西还有很多,这里我们简单的提一下概念,让大家有个了解,关于这方便更详细的可以阅读世界级软件开发大师Martin Fowler的《领域特定语言》,这本书介绍的非常详细。