1. 前言
最近開始忙起來了,寫東西的時間越來越少了。這幾天開始在 Java 開發中嘗試函數式編程風格。所以就寫點小東西來分享一下。
2. 什么是函數式編程?
在我看來函數式編程就是把函數作為一等公民來使用。平常我們開發都是在處理數據。面向對象中都在處理類。而函數式編程不是在寫函數就是在寫函數的路上。我們來看看 Java 中函數式編程的演進之路。我小時候玩過一種廉價的黑白屏游戲機,只能玩俄羅斯方塊。這種是不可擴展的。
public void playGame(){
// 只能玩俄羅斯方塊
}
這種機器給童年帶來了不少的樂趣。后來小伙伴有了一臺gameboy,這種掌機的好處在于它可以插卡。一張卡一個游戲,最有名的莫過于 《超級馬里奧》 和《打磚塊》了。這時候機器是完全可擴展的了。
public void playGameboy(GameboyCard card){
card.getGame().run()
}
雖然可以擴展但是卡的價格非常貴,當時買卡的渠道還很少。那時候其實就想如果能自己造 gameboy 游戲就好了(然后我就來搞編程?)。理想中的游戲機是我們不關心你什么風格的游戲,只要你能放入符合接口的游戲卡中并且在我這個游戲機中跑就行。
所以我們定義了一個固定的游戲卡接口:
/**
* @author Felordcn
* @since 2019/10/31 22:13
*/
@FunctionalInterface
public interface Card {
Game apply();
}
只要符合這種接口的游戲卡都能插到機器中玩:
/**
* Fun.
*
* @param card the card
*/
void fun(Card card) {
Game game = card.apply();
game.run();
}
熟練面向對象的同學們會說這不就是面向接口編程嗎?是的你說的沒有問題。但是這里 Card
接口只干一件事就是提供游戲。我們的重心是游戲卡嗎?顯然不是!有趣好玩的游戲才是我們的目的所在。于是我們不管他是卡還是光碟甚至網路,只要能提供游戲給我們娛樂都符合我們的需要。
// 玩插卡游戲機
fun(() -> new CardGame());
// 玩 PSP
fun(() -> new PSPGame());
// more
作為一名碼農,平常我們都在寫 SQL。 無論大廠小廠,不管單體還是分布式。SQL 總能幫我們解決很多業務關系處理。SELECT
、 INSERT
、 UPDATE
、 DELETE
每一種命令只要是 SQL 規范數據庫,不管是什么表都會是一致的操作。你聲明了什么命令就執行什么操作。這時數據與函數是松耦合的。正是這樣的特點讓我們實現了“萬變不離其宗”。這也是一種另類的函數式編程。
3. 面向對象和函數式編程沖突嗎?
面向對象一直處于我能操作什么數據、這種數據我該怎么操作的范式中。而函數式編程一直沉浸于給我操作數據的方法中。面向對象最大優點是多態性和封裝;函數式編程優勢是抽象化和聲明式命令風格,兩者其實是正交,可互補的,可在同一程序中共存。爭論是面向對象好還是面向函數好跟爭論哪門語言好一樣都是非常極端的。對于面向對象來講:存在的并不一定都是對象,函數就是對象;對于函數式編程來說:存在的并不總是純粹的,副作用總是真實存在的。總之,面向對象側重于分解,函數編程側重于組合。
4. 函數式編程特點
函數風格的編程擁有自己的一些特點:
- 函數作為一等公民。 可以作為參數傳遞、從函數里返回、可以賦值給變量。
- 帶有閉包的 Lambda 表達式和匿名函數,這是廣泛的多態。
- 不變性,大部分無態處理,在函數式程序中,變量是通過外部傳入或者申明獲得值的。變量不能被改變
- 基于不可變進而可以無副作用的調用。
- 通過
tail call
實現遞歸的性能優化。 - 提供動態的、可組合的開發思路。
5. 總結
今天簡單表達了我對函數式編程的一些理解,對于習慣了面向對象的 Java 開發者來說,理解函數式編程并不容易。它不僅僅有 Lambda 和匿名函數!更多的是一種思想。這里推薦一個很好的 Java 函數式編程庫 vavr 。有興趣的同學可以學習下。
關注公眾號:碼農小胖哥,獲取更多資訊