引子
我們用一段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/