引子
我們用一段gradle的腳本做引子,理解這一段腳本與一般的groovy代碼是怎么聯系起來的
buildscript {
repositories {
jcenter()
mavenLocal()
//或者使用指定的本地maven 庫
maven{
url "file://D:/repo"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
DSL的定義
DSL(Domain Specific Language)定義:針對某一領域,具有受限表達性的一種計算機程序設計語言。
所謂針對某一領域,其基本思想是“求專不求全”,不像通用目的語言那樣目標范圍涵蓋一切軟件問題,而是專門針對某一特定問題的計算機語言。
DSL伴隨語義模型出現,語義模型會表現為程序庫或者框架,對於構建DSL而言,語義模型不可或缺。DSL只是位於其上的一層而已。定義做什么,而不是用一堆命令語句來描述怎么做,所以它是聲明式編程(如SQL),這一點很重要。DSL的受限表達性可以使DSL語言不易出錯,即便出錯,也易於發現。這是受限表達性的意義。
DSL是通用語言的特定用法。內部DSL通常是一段合法的程序,但是具有特定的風格。而且只用到了語言一部分特性。防止DSL逐漸演變為一種通用語言,要受限表達。目的防止DSL過於復雜,可維護性降低,學習成本提升,偏離方向。不要讓DSL讀起來向自然語言。它是程序語言,比自然語言更加准確和簡潔。語義模型位於語言和DSL之間,為二者解耦。DSL腳本,解析器,語義模型,模型——DSL自上而下幾個層次。
Groovy語法對DSL的支持
我們先看一個簡單的類和一個奇葩的語法現象:分號省略;默認public省略;有形參的方法調用,括號可以省略;返回的return可以省略,默認最后一行代碼的值返回。
import groovy.xml.*
import java.io.*
class Task{
//默認省略public和分號
String summary
String description
Date dueDate
Map m
static void main(args){
//默認有set和get方法
Task task1 = new Task()
task1.setSummary("this is Task1")
println task1.getSummary()
//有形參的方法調用,括號可以省略
task1.setDescription "this is Task class"
task1.printDescription ""
//可以直接傳map
Task task3 = new Task()
task3.setM (['summary':'this is Task3','description':'Task'])
println task3.getM()
//map的分號可以省略
Task task2= new Task('summary':'this is Task2','description':'Task')
println task2.getSummary()
//括號也可以省略
Task task4 = new Task()
task4.setM 'summary':'this is Task4'
println task4.getM()
}
public void printDescription(def str){
println "the task description is : $description"
}
}
看完省略括號的語法現象,下面看另一個重量級的語法現象——閉包
閉包是用{符號括起來的代碼塊,它可以被單獨運行或調用,也可以被命名。類似‘匿名類’或內聯函數的概念。
閉包中最常見的應用是對集合進行迭代,下面定義了3個閉包對map進行了迭代:
map.each({key,value-> //key,value兩個參數用於接受每個元素的鍵/值
println "$key:$value"})
map.each{println it} //it是一個關鍵字,代表map集合的每個元素
map.each({ println it.getKey()+"-->"+it.getValue()})
除了用於迭代之外,閉包也可以單獨定義:
def say={word->
println "Hi,$word!"
}
調用:
say('groovy')
say.call('groovy&grails')
輸出:
Hi,groovy!
Hi,groovy&grails!
看起來,閉包類似於方法,需要定義參數和要執行的語句,它也可以通過名稱被調用。然而閉包對象(不要奇怪,閉包也是對象)可以作為參數傳遞(比如前面的閉包作為參數傳遞給了map的each方法)。而在java中,要做到這一點並不容易(也許C++中的函數指針可以,但不要忘記java中沒有指針)。其次,閉包也可以不命名(當然作為代價,只能在定義閉包時執行一次),而方法不可以。
當閉包遇到括號省略,一切都不一樣了
Project.groovy
public class Project{
Date date
void setDateFormat(Closure formatDate){
println formatDate(date)
}
}
Main.groovy
public class Main{
public static void main(def args){
Project p = new Project()
p.setDate new Date()
//正常
p.setDateFormat({
return it.format('yyyy-MM-dd HH:mm:ss')
})
//減return
p.setDateFormat {
it.format('yyyy-MM-dd HH:mm:ss')
}
//減括號(是不是很像我們的gradle腳本?)
p.setDateFormat {
it.format 'yyyy-MM-dd HH:mm:ss'
}
}
}
減完之后,像不像下面的腳本?
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
唯一的區別是我們需要用對象來引用方法,其實去掉對象也不難,自己調用自己的方法就可以了,看下面的代碼:
public class Project{
Date date
void setDateFormat(Closure formatDate){
println formatDate(date)
}
//將format內置
String format(String f){
date.format(f)
}
//無形參的方法
void showDate(){
print date.toString()
}
void run(){
//對象去掉,是不是一模一樣了?
setDateFormat{
println 'this is a scrip'
format 'yyyy-MM-dd HH:mm:ss'
//沒有形參的話就只能乖乖寫括號了
showDate()
}
}
}
DSL的兩個關鍵點,某一領域,gradle只為編譯,也只用於編譯。受限表達,一般只調用腳本運行上下文環境中的方法,為的就是盡量簡單,出錯的話,排錯方便。伴隨而生的語義模型就是那一套編譯框架。
Groovy對DSL的支持,表現為可以省略:分號,調用方法的括號,return,默認public等。
參考:
http://docs.groovy-lang.org/latest/html/documentation/
