Android 開發者如何函數式編程 (三)

在上一章,咱們學習了不可變性併發。在這一章,咱們將學習高階函數閉包前端

若是你尚未閱讀過第一部分和第二部分,能夠點擊這裏閱讀:react

高階函數

高階函數是能夠接受將函數做爲輸入參數,也能夠接受將函數做爲輸出結果的一類函數。很酷吧?android

可是爲何有人想要那樣作呢?ios

讓咱們看一個例子。假設我想壓縮一堆文件。我想用兩種壓縮格式來作 — ZIP 或者 RAR 格式。若是用傳統的 Java 來實現,一般會使用 策略模式git

首先,建立一個定義策略的接口:github

public interface CompressionStrategy {
    void compress(List<File> files);
}
複製代碼

而後,像如下代碼同樣實現兩種策略:編程

public class ZipCompressionStrategy implements CompressionStrategy {
    @Override public void compress(List<File> files) {
        // Do ZIP stuff
    }
}
public class RarCompressionStrategy implements CompressionStrategy {
    @Override public void compress(List<File> files) {
        // Do RAR stuff
    }
}
複製代碼

在運行時,咱們就可使用任意一種策略:後端

public CompressionStrategy decideStrategy(Strategy strategy) {
    switch (strategy) {
        case ZIP:
            return new ZipCompressionStrategy();
        case RAR:
            return new RarCompressionStrategy();
    }
}
複製代碼

使用這種方式有一堆的代碼和須要遵循的格式。bash

其實咱們所要作的只是根據不一樣的變量實現兩種不一樣的業務邏輯。因爲業務邏輯不能在 Java 中獨立存在,因此必須用類和接口去修飾。閉包

若是可以直接傳遞業務邏輯,那不是很好嗎?也就是說,若是能夠把函數看成變量來處理,那麼可否像傳遞變量和數據同樣輕鬆地傳遞業務邏輯?

正是高階函數的功能!

如今,從高階函數的角度來看這同一個例子。這裏我要使用 Kotlin ,由於 Java 8 的 lambdas 表達式仍然包含了咱們想要避免的 一些建立函數接口的方式

fun compress(files: List<File>, applyStrategy: (List<File>) -> CompressedFiles){
    applyStrategy(files)
}
複製代碼

compress 方法接受兩個參數 —— 一個文件列表和一個類型爲 List<File> -> CompressedFilesapplyStrategy 函數。也就是說,它是一個函數,它接受一個文件列表並返回 CompressedFiles

如今,咱們調用 compress 時,傳入的參數能夠是任意接收文件列表並返回壓縮文件的函數。:

compress(fileList, {files -> // ZIP it})
compress(fileList, {files -> // RAR it})
複製代碼

這樣代碼看起來乾淨多了。

因此高階函數容許咱們傳遞邏輯並將代碼看成數據處理。

閉包

閉包是能夠捕捉其環境的函數。讓咱們經過一個例子來理解這個概念。假設給一個 view 設置了一個 click listener,在其方法內部想要打印一些值:

int x = 5;

view.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        System.out.println(x);
    }
});
複製代碼

Java 裏面不容許咱們這樣作,由於 x 不是 final 的。在 Java 裏 x 必須聲明爲 final,因爲 click listener 可能在任意時間執行, 當它執行時 x 可能已經不存在或者值已經被改變,因此在 Java 裏 x 必須聲明爲 final。Java 強制咱們把這個變量聲明爲 final,其實是爲了把它設置成不可變的。

一旦它是不可變的,Java 就知道無論 click listener 何時執行,x 都等於 5。這樣的系統並不完美,由於 x 能夠指向一個列表,儘管列表的引用是不可變的,其中的值卻能夠被修改.

Java 沒有一個機制可讓函數去捕捉和響應超過它做用域的變量。Java 函數不能捕捉或者涵蓋到它們環境的變化。

讓咱們嘗試在 Kotlin 中作相同的事。咱們甚至不須要匿名內部類,由於在 Kotlin 中函數是「一等公民」:

var x = 5

view.setOnClickListener { println(x) }
複製代碼

這在 Kotlin 中是徹底有效的。Kotlin 中的函數都是閉包。他們能夠跟蹤和響應其環境中的更新。

第一次觸發 click listener 時, 會打印 5。若是咱們改變 x 的值好比令 x = 9,再次觸發 click listener ,此次會打印9

咱們能利用閉包作什麼?

閉包有不少很是好的用例。不管什麼時候,只要你想讓業務邏輯響應環境中的狀態變化,那就可使用閉包。

假設你在一個按鈕上設置了點擊 listener, 點擊按鈕會彈出對話框向用戶顯示一組消息。若是沒有閉包,則每次消息更改時都必須使用新的消息列表而且初始化新的 listener。

有了閉包,你能夠在某個地方存儲消息列表並把列表的引用傳遞給 listener,就像咱們上面作的同樣,這個 listener 就會一直展現最新的消息。

**閉包也能夠用來完全替換對象。**這種用法常常出如今函數式編程語言的編程實踐中,在那裏你可能須要用到一些 OOP(面向對象編程)的編程方法,可是所使用的語言並不支持。

咱們來看個例子:

class Dog {
    private var weight: Int = 10

    fun eat(food: Int) {
        weight += food
    }

    fun workout(intensity: Int) {
        weight -= intensity
    }

}
複製代碼

我有一條狗在餵食時體重增長,運動時體重減輕。咱們能用閉包來描述相同的行爲嗎?

fun main(args: Array<String>) {
   dog(Action.feed)(5)
}
val dog = { action: Action ->
    var weight: Int = 10
when (action) {
        Action.feed -> { food: Int -> weight += food; println(weight) }
        Action.workout -> { intensity: Int -> weight -= intensity; println(weight) }
    }
}
enum class Action {
    feed, workout
}
複製代碼

dog 函數接受一個 Action 參數,這個 action 要麼是給狗餵食,要麼是讓它去運動。當在 main 中調用 dog(Action.feed)(5),結果將是 15dog 函數接受了一個 feed 動做,並返回了另一個真正去給狗餵食的函數。若是把 5 傳遞給這個返回的函數,它將把狗狗的體重增長到 10 + 5 = 15 並打印出來。

因此結合閉包和高階函數,咱們沒有使用 OOP 就有了對象。

可能你在真正寫代碼的時候不會這樣作,可是知道能夠這樣作也是蠻有趣的。確實,閉包被稱爲可憐人的對象

總結

在許多狀況下,相比於 OOP 高階函數讓咱們能夠更好地封裝業務邏輯,咱們能夠將它們當作數據同樣傳遞。閉包捕獲其周圍環境,幫助咱們有效地使用高階函數。

在下一部分,咱們將學習如何以函數式的方法去處理錯誤。

若是你喜歡這篇文字,能夠點擊下面的 :clap: 按鈕。我通知了他們每個人,我也感激他們每個人。

感謝 Abhay Soods0h4m.

掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索