Groovy Tutorial(3)淺談 Closure 程式設計
Groovy Tutorial(2)軟體安裝與開發環境設定 << 前情 認識 Closure 是設計 Groovy 程式的重要基礎,本篇提供淺顯易懂的範例說明,希望幫助讀者更快掌握 Closure 的觀念,並活用在 Groovy 程式的撰寫。開始使用 Closure 設計 Groovy 程式,可以發現 Groovy 不只簡化 Java 的語法,它也許還能啟發 Java 開發者不同的設計思維。 Groovy JDK 與 Closure 參數Groovy 擴充 Java SE API,提供一組 Groovy JDK(簡稱為 GDK),它讓 Java 類別具有更豐富的功能。 被擴充的類別,具有與 Java 類別相同的命名,包括套件名稱(package name)與類別名稱(class name),例如「 GDK 提供的同名類別,稱為 Helper Class(擴充自 Java 原有的類別),查閱 Groovy SDK 的 API 文件,可以了解有哪些類別及方法可供使用。 Helper Class 讓程式撰寫風格可以 Groovier(更有 Groovy 程式的風味),明顯的例子就是使用那些包含 Closure 參數的方法。 例如
許多 GDK 類別的方法都使用 Closure 型別的參數,所以認識 Closure 才能活用 GDK API 設計程式。 那麼這些 Closure 參數到底如何使用呢? 初學者容易產生誤會,看到文件可能誤以為需要 確實可以這樣做,但這並不是正常的 Groovy 程式撰寫方式。 認識 Closure在 Groovy 的世界中,Closure 是一種 first class object,類似 JavaScript 的匿名函式(anonymous function)。Groovy 使用「 例如,計算平方的函式,可以使用 Closure 改寫。 def square = { x -> x * x } square(3) Groovy 的 Closure 有「Code as data」的特性,實際上它會建立 參考以下範例,三個 class MyClass { def num def calc(Closure closure) { closure(num) } def calc(n, Closure closure) { closure(n) } def calc(n1, n2, Closure closure) { closure(n1, n2) } }
先產生一個 mc1 = new MyClass(num: 5) 在存取 mc1.calc { x -> x * x } 增加一個參數,根據多載函式的規則,會執行 mc1.calc(3) { x -> x * x } 計算 x 的 y 次方,執行結果 25 是 5 的 3 次方。 mc1.calc(5, 3) { x, y -> (int)Math.pow(x, y) } 使用 Closure 的檔案處理範例有了 GDK 提供 Helper Class 的幫助,使用 file1 = new File('output.log') file1.setText('hello\n') 只帶一個參數的 file1.text = 'hello\n' 使用 content = file1.getText() 同樣也能把 Getter 方法的「get」省略。 content = file1.text 撰寫 Groovy 程式時,讓程式碼看起來更簡潔是件愉快的事。 但是如果遇到一邊讀檔一邊處理的需求時,怎麼做才是 Groovy 程式的風格呢? 又輪到 Closure 上場囉! 許多 GDK 的 Helper Class 利用 Closure 參數擴充原有的類別,在 如果需要逐行處理文字檔的內容,使用 new File('abc.txt').eachLine { line -> println line } 在 eachLine 後面加上 Closure 就能搞定,這是 Groovy 程式的風格! 使用 如果省略 Closure 中的參數命名,會使用「it」當作預設參數名稱。以下利用 def stats = new File('stats.log') stats.withWriter { println it.class.name println it instanceof BufferedWriter println it instanceof Writer } 由於 以下範例程式將數字 1 至 10 依序寫入檔案。 stats.withWriter { writer -> (1..10).each { writer << "${it}\n" } } 利用 Closure 撰寫 Groovy 程式,不只是一種風格,也帶來更靈活的程式設計思考方式。 舉例來說,我們可以設計一個程式,讓使用者動態選擇、增加不同的資料處理程序(processors);這些程序可以用 Closure 方式設計,不僅是可以被執行的函數,也能當作資料保存在 List 中即當作參數傳遞。 以下分別定義不同的 Closure 程序,計算平方根與平方。Closure 被當作資料放入 def processors = [] processors << { line -> println Math.sqrt(line.toInteger()) } processors << { line -> println Math.pow(line.toInteger(), 2).toLong() } processors.each { stats.eachLine it } 更多 Closure 的程式設計範例使用 存取串流或資料庫等資源時,經常會有類似的操作,通常我們需要小心翼翼地使用 try-catch-finally 敘述來處理。 為了說明如何用 Closure 解決問題,我們先設計一個
class MyStream { def open() { println 'open stream' } def close() { println 'close stream' } def read() { println 'read action' } def write(obj) { println "write action with ${obj}" } def exception() { throw new Exception('bad news') } } 爲免於煩人的 try-catch-finally 不斷出現,我們定義一個 def use = { MyStream stream, Closure closure -> try { stream.open() closure(stream) } catch (e) { println e.message } finally { stream.close() } } 使用 def stream1 = new MyStream() use(stream1) { it.read() it.write(new Object()) it.exception() } 再來看一個簡單的範例,我們讓 List 增加新的功能,讓新增加到 List 的資料先經過篩選,只有符合條件的資料才被允許加入,否則直接忽略丟棄。 class ListWithFilter extends ArrayList { Closure filter def leftShift(obj) { if (filter && filter(obj)) add(obj) } }
filters = [:] filters.isOdd = { it % 2 != 0 } filters.isEven = { it % 2 == 0 } 產生 def list1 = new ListWithFilter(filter: filters.isOdd) (1..100).each { list1 << it } println list1 參考資料 |