動態

詳情 返回 返回

Android開發中的函數式編程應用:面向組合子編程 - 動態 詳情

1. 聲明式編程

聲明式範式和命令式範式是不同的。命令式編程專注於“如何一步步完成任務”,開發者需要詳細地指示如何執行每一個步驟,比如Android的xml layout開發,開發者必須指定所有View的層次結構,自己調整View的屬性以獲得更好的性能。
而聲明式編程則關注於“期望的結果是什麼”,而具體這個結果如何達到、如何實現則不會明確編寫,而是交給框架來處理。比如數據庫的SQL,我們對於數據庫只需要描述我們希望檢索的條件,數據庫系統會按照自己的方式來進行實現。

這相當於把“描述”和“實現”兩部分給分開了,好處在於“描述”可以專注於描述需求,而“實現”可以專注於功能實現,而“實現”如果後續有升級、重構對於“描述”而言不需要修改任何代碼即可獲得這種升級。

Jetpack Compose之類的框架逐漸流行,正是聲明式編程範式強大的體現。與傳統的XML佈局的命令式方式(開發者需要手動操作View層級和狀態變化)不同,Compose允許開發者聲明期望的UI狀態,框架則負責管理其渲染。開發者不再需要自己進行layout的結構優化、狀態管理等等,Compose如果對於View結構有優化、完全可以在底層自動完成。
這種方式也減少了樣板代碼,提高了代碼的可讀性和可維護性 。

擴展來説,也是一種解耦合的思想,每個部分專注於自己的功能開發、相互隔離。

2. 再談函數式編程

我們在學習計算機的初期,就聽過,程序=數據結構+算法。但傳統的命令式、OOP編程都將數據結構和算法相互融合了。而函數式編程迴歸到了本源:

  • 數據結構: 代數數據結構
  • 算法:函數、類型類(Typeclass)

函數式編程中,沒有繼承、也沒有類的概念,所以也沒有屬性的概念。

我們構造代數數據結構、創建函數操作這些數據結構,就構成了一個程序,而多個函數聲明的集合就構成了類型類(Typeclass)(類似於接口),一個數據結構希望具有某個能力,就提供類型類的實例。

這種方式避免了類的內部私有屬性對於結果的副作用,也由於數據被完全提取到了最外層,實現了狀態的集中管理,避免了可變狀態的不確定性。

3. 什麼是組合子

組合子編程是函數式編程的基礎方法。“組合子”指的是某個業務邏輯中最基礎的原件,它們類似於樂高積木,我們通過不斷組合這些一個個簡單的基礎積木就可以構建出複雜的結構。
有人給出了一個定義:“一個組合子是一個從程序片段構建程序片段的函數”

  • 沒有自由變量: 不依賴於其作用域之外的任何變量,這使得它們成為純函數。
  • 重點在於相互之間的組合

面向對象編程中,我們傾向於將狀態、方法封裝於一個類中,通過對象方法來實現交互,強調對象與方法的調用。
而面向組合子編程中,則更側重於函數之間的組合、數據在函數之間進行流動。

組合子對純函數的強調以及不依賴外部依賴的特性,使得代碼更加可預測和易於測試,這有助於解決安卓應用中常見的狀態管理和副作用處理方面的挑戰。

4. 組合替代繼承

面向對象編程中強調相互之間的繼承關係,實際是在描述“is-a”的關係。但深層的繼承結構、單繼承、緊耦合、脆弱基類等等問題也常常困擾着開發者。

而函數式編程中本身就沒有“繼承”,功能的擴展都是通過函數的組合實現。這種方式使得功能的擴展更為靈活。

5. 如何設計組合子

組合子是一個業務邏輯中所有需求的最根本、最簡單的“元”。一般來説這種元並不多,而且相互是“正交”的(互相不可替代)。
而為了組合性,一般我們也會讓這些組合子需要滿足常見的性質(比如Monad)
組合子本身只是一系列函數,不會包含數據結構。我們還會用這些基礎組合子組合出複雜的結構。

6. 示例

6.0 通用語法分析器

這是非常典型的函數式編程中組合子設計的案例,可以參考樂享文章。

6.1 SimpleSharedPref

https://github.com/Yumenokanata/SimpleSharedPref
它是用於SharedPreferences的包裝,實際它要解決的只是如何將各種複雜的數據類型映射到SharedPreferences中支持的幾種特定的保存數據類型中。
“組合子”就是用於組合各種類型轉換的邏輯。
它的核心數據就是SharedField<T>,其中包含兩個Reader類型。而Pipe<A, B>是用於轉換SharedField類型的中間類型。
使用上,實際就是對SharedField內部的Reader進行變換來實現類型映射。
但需要注意,SharedField並不滿足Monad法則。

6.2 DslAdapter

https://github.com/Yumenokanata/DslAdapter
它用於組合RecyclerView的數據結構,以實現強類型條件下的複雜數據結構映射到RecyclerView的單層列表數據結構。
它的基礎組合子是BaseRenderer。BaseRenderer中並不包含任何子狀態,它只是一系列函數的集合,而全局只有一個狀態,被嚴密地保存於RendererAdapter中。(體現了數據和算法分離的思想)

6.3 YRoute

https://github.com/Yumenokanata/YRoute
這是一個用於導航的庫,主要在於將Activity和Fragment的生命週期、跳轉等行為包裝為響應式。有着很高的自由度。

7. 由下而上 和 由上而下

在OOP編程中,我們強調先進行頂層的結構設計,再逐步細化出底層結構。
而函數式編程中,則強調先找到問題最底層的基礎抽象,再由下層的基礎部件逐漸構建出複雜的上層結構。

8. 總結

在實際Android開發中,由於面對的接口、方法都是OOP的,所以很多時候設計中需要做出一定的妥協。但是即使如此,面向組合子的編程方法仍然展現了非凡的的能力。

  • 狀態隔離(獨立)
  • 構造基礎組合子

相比於普通接口的設計模式,它具有組合性高、重用性高、擴展性強、耦合度低的特點。

user avatar hejing-michael 頭像 maenj_ba_lah 頭像 stars-one 頭像 fengdudeyema 頭像 wodekouwei 頭像 reddish 頭像 wanlanqiudehuoche_ej0yz4 頭像 wennuandedasuan_c6gnoc 頭像 jingmingdewudongmian_dscnyw 頭像 manshenjiroudexiangjiao_bogavh 頭像 xingxingshangdeliushu 頭像 jizaodebangbangtang 頭像
點贊 14 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.